send (smtp) and retrieve (pop3) email with ease under vb.net

60
Page –1– Send (SMTP) and Retrieve (POP3) Email with Ease under VB.NET By David Ross Goben Copyright © 2011 by David Ross Goben All rights reserved. Last Update: Monday, July 16, 2012 This is a sample excerpt from the free PDF e-book, Enhancing Visual Basic .NET Far Beyond the Scope of Visual Basic 6.0, by David Ross Goben. Download this e-book, and its free companion, Navigating Your Way Through Visual Basic 6.0 Upgrades to Visual Basic .NET, also by David Ross Goben, at www.slideshare.net/davidrossgoben . They are also available on Google Docs at https://docs.google.com/leaf?id=0B_Dj_dKazINlN2JlY2EwMmEtNGUyMy00NzQzLTliN2QtMDhlZTc5NDUzY2E5&sort=name&layout=list&num =50 . The Google site also has a VBNetEmail.zip source file package featuring all VB.NET classes and utilities. Table of Contents Send (SMTP) and Retrieve (POP3) Email with Ease under VB.NET................................................................................... 2 Adding the VB6 MAPISession and MAPIMessage Controls to VB.NET............................................................................................................ 2 PART ONE ▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬ Sending Email under VB.NET using Native Methods ................................................................................................................................ 3 Quick and Dirty Email Senders .......................................................................................................................................................... 3 TCP Ports, SSL Authentication, and Creating Credentials ............................................................................................................... 5 An Email Sender with a Lot of Muscle ............................................................................................................................................... 6 Sending Email Messages as HTML .................................................................................................................................................. 8 Sending Alternate Message Views .................................................................................................................................................. 10 Sending Alternate Message Views with Different Context Types and Transfer Encoding ............................................................ 13 Typical Email Server Specifications .......................................................................................................................................................... 17 PART TWO ▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬ Encoding and Decoding Email Data ......................................................................................................................................................... 18 Allowing Users to Specify Content-Type and Content-Transfer-Encoding Options....................................................................... 19 Determining if Text can be Encoded As Quoted-Printable, Base64, or 7Bit .................................................................................. 21 Converting 8-Bit HTML Data to 7-Bit without Loss of Integrity ....................................................................................................... 21 Converting 8-Bit Text Data to 7-Bit without Data Loss .................................................................................................................... 22 Decoding Quoted-Printable Text ...................................................................................................................................................... 23 Translating Base64 Data Back to Its Original Format ..................................................................................................................... 23 Translating BinHex Data Back to its Original Format ...................................................................................................................... 26 PART THREE ▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬ Receiving Email under VB.NET using Native Methods ............................................................................................................................ 27 Connecting to a POP3 Server .......................................................................................................................................................... 30 Checking for a POP3 Server Response .......................................................................................................................................... 31 Checking for Being Connected to a POP3 Server .......................................................................................................................... 31 Getting a Response from the POP3 Server .................................................................................................................................... 31 Submitting a Request to the POP3 Server ...................................................................................................................................... 33 Disconnecting from the POP3 Server .............................................................................................................................................. 33 Getting Email Statistics from the POP3 Server ............................................................................................................................... 34 Getting an Email Reference List from the POP3 Server ................................................................................................................. 34 Get an Email Header from the POP3 Server................................................................................................................................... 35 Retrieve an Email from the POP3 Server ........................................................................................................................................ 36 Deleting an Email from the POP3 Server ........................................................................................................................................ 36 Reset (Undo) All Deletes from the POP3 Server ............................................................................................................................ 37 Send a ‘Keep-Alive’ NOOP Command to the POP3 Server ........................................................................................................... 37 Disposing of Resources ................................................................................................................................................................... 37 Using the Completed POP3 Class ................................................................................................................................................... 38 PART FOUR ▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬ Email Data Blocks Made Easy .................................................................................................................................................................. 39 Easily Extracting the Component Parts from an Email File ............................................................................................................. 42 Compiling Everything into an Email Class Library .................................................................................................................................... 44 Building the VBNetMail Class Library ............................................................................................................................................... 46 Accessing your New VBNetEmail Class Library DLL from another Project .................................................................................... 46 The Complete SMTP.VB File .................................................................................................................................................................... 47 The Complete POP3.VB File..................................................................................................................................................................... 47 The Complete Utilities.VB File................................................................................................................................................................... 53 Conclusion................................................................................................................................................................. 58 About the Author .............................................................................................................................................................. 59

Upload: david-g

Post on 06-May-2015

18.107 views

Category:

Technology


2 download

DESCRIPTION

This sample excerpt show you how to easily process, encode, and decode email. It also shows you how to easily perform complex outbound SMTP email, and introduces an inbound POP3 email class that is missing in VB.NET.

TRANSCRIPT

Page 1: Send (SMTP) and Retrieve (POP3) Email with Ease under VB.NET

Page –1–

Send (SMTP) and Retrieve (POP3) Email with Ease under VB.NET

By David Ross Goben Copyright © 2011 by David Ross Goben All rights reserved.

Last Update: Monday, July 16, 2012

This is a sample excerpt from the free PDF e-book, Enhancing Visual Basic .NET Far Beyond the Scope of Visual Basic 6.0, by David

Ross Goben. Download this e-book, and its free companion, Navigating Your Way Through Visual Basic 6.0 Upgrades to Visual Basic

.NET, also by David Ross Goben, at www.slideshare.net/davidrossgoben. They are also available on Google Docs at https://docs.google.com/leaf?id=0B_Dj_dKazINlN2JlY2EwMmEtNGUyMy00NzQzLTliN2QtMDhlZTc5NDUzY2E5&sort=name&layout=list&num

=50. The Google site also has a VBNetEmail.zip source file package featuring all VB.NET classes and utilities.

Table of Contents Send (SMTP) and Retrieve (POP3) Email with Ease under VB.NET................................................................................... 2 Adding the VB6 MAPISession and MAPIMessage Controls to VB.NET............................................................................................................2

PART ONE Sending Email under VB.NET using Native Methods ................................................................................................................................3 Quick and Dirty Email Senders ..........................................................................................................................................................3 TCP Ports, SSL Authentication, and Creating Credentials ...............................................................................................................5 An Email Sender with a Lot of Muscle...............................................................................................................................................6 Sending Email Messages as HTML ..................................................................................................................................................8 Sending Alternate Message Views ..................................................................................................................................................10 Sending Alternate Message Views with Different Context Types and Transfer Encoding ............................................................13 Typical Email Server Specifications ..........................................................................................................................................................17

PART TWO Encoding and Decoding Email Data .........................................................................................................................................................18 Allowing Users to Specify Content-Type and Content-Transfer-Encoding Options.......................................................................19 Determining if Text can be Encoded As Quoted-Printable, Base64, or 7Bit ..................................................................................21 Converting 8-Bit HTML Data to 7-Bit without Loss of Integrity .......................................................................................................21 Converting 8-Bit Text Data to 7-Bit without Data Loss....................................................................................................................22 Decoding Quoted-Printable Text......................................................................................................................................................23 Translating Base64 Data Back to Its Original Format .....................................................................................................................23 Translating BinHex Data Back to its Original Format......................................................................................................................26

PART THREE Receiving Email under VB.NET using Native Methods............................................................................................................................27 Connecting to a POP3 Server..........................................................................................................................................................30 Checking for a POP3 Server Response ..........................................................................................................................................31 Checking for Being Connected to a POP3 Server ..........................................................................................................................31 Getting a Response from the POP3 Server ....................................................................................................................................31 Submitting a Request to the POP3 Server ......................................................................................................................................33 Disconnecting from the POP3 Server ..............................................................................................................................................33 Getting Email Statistics from the POP3 Server ...............................................................................................................................34 Getting an Email Reference List from the POP3 Server.................................................................................................................34 Get an Email Header from the POP3 Server...................................................................................................................................35 Retrieve an Email from the POP3 Server ........................................................................................................................................36 Deleting an Email from the POP3 Server ........................................................................................................................................36 Reset (Undo) All Deletes from the POP3 Server ............................................................................................................................37 Send a ‘Keep-Alive’ NOOP Command to the POP3 Server ...........................................................................................................37 Disposing of Resources ...................................................................................................................................................................37 Using the Completed POP3 Class...................................................................................................................................................38

PART FOUR Email Data Blocks Made Easy ..................................................................................................................................................................39 Easily Extracting the Component Parts from an Email File .............................................................................................................42 Compiling Everything into an Email Class Library....................................................................................................................................44 Building the VBNetMail Class Library ...............................................................................................................................................46 Accessing your New VBNetEmail Class Library DLL from another Project ....................................................................................46 The Complete SMTP.VB File ....................................................................................................................................................................47 The Complete POP3.VB File.....................................................................................................................................................................47 The Complete Utilities.VB File...................................................................................................................................................................53

Conclusion................................................................................................................................................................. 58 About the Author .............................................................................................................................................................. 59

Page 2: Send (SMTP) and Retrieve (POP3) Email with Ease under VB.NET

Page –2–

Send (SMTP) and Retrieve (POP3) Email with Ease under VB.NET Brays whisper distantly beneath midnight mists as spent developers, pinned by looming deadlines and their

brain’s threat of total collapse, rasp desperate prayers against an ominous sense of impending doom, working

at a fever’s pitch to hammer out viable code (which, according to Murphy’s Law of Looming Deadlines, is an

absolute impossibility). Suddenly, in utter horror, they crash into a brick wall; and this after having assured

their skeptical client of how superior VB.NET was over that antiquated VB6 the client revered. They realize

too late that their strategy for the client’s email handler is unworkable: VB.NET does not provide the MAPI

controls they thought it did. Another soul-torn howl trebles against the muffled parapets of the valley.

Plain and simple, MAPI (Messaging Application Program Interface) is not yet a .NET technology; it is still

COM technology (Common Object Model), used by ASP (Active Server Page), IIS (Internet Information Server), and any other COM application that access email. Being COM-based, you should not expect to see

the VB.NET Toolbox sport controls such as VB6’s MAPISession or MAPMessage.

Under the VB6 implementation of MAPI, it used the MAPISession control to (what else?) manage a MAPI

session. The MAPIMessage control was used to process email messages, both incoming (POP3; Post Office Protocol – Version 3) and outgoing (SMTP; Simple Mail Transfer Protocol).

Presently, .NET is set up for outbound email, but it lacks a class supporting inbound email, even though both

of these technologies are simple TCP Clients (Transmission Control Protocol). I think it may have

something to do with many people wanting to read email using eye-candy apps, such as Outlook or Mail. But

this does not remove the need for an inbound class in more controlled environments.

Although it is easy to write VB.NET code to support POP3 Inbound Email services, as I will show you, I

have found only one other person (I have since found more, but none provide robust solutions), and he works

at Microsoft, who has developed any sort of VB.NET code to demonstrate this ability (albeit his solution was

just a simple example with very limited capability). But by the time I found his article within the catacombs

of MSDN, I had already put the finishing touches on my own full-featured POP3 Inbound Email solution.

Adding the VB6 MAPISession and MAPIMessage Controls to VB.NET But before diving into these VB.NET solutions, let us first take a look at the kind support that is presently

available to developers who are upgrading VB6 MAPI applications to VB.NET.

When you upgrade a VB6 MAPI application to VB.NET, you will notice that the upgraded

application will still have the VB6 MAPISession and MAPIMessage controls on any form that

had them before. This is because their control sources have been copied locally and are

referenced internally. Under .NET, a copy of the COM-based MSMAPI32.DLL is converted into

a non-COM version (its DLLRegisterServer() entry is disabled) and saved to a project-local file named

Interop.MSMAPI32.DLL. But, because both VB6 controls actually accessed this DLL provider through the

MSMAPI32.OCX ActiveX interface, another project-local non-COM DLL named AxInterop.MSMAPI32.DLL

is internally compiled by .NET that will duplicate both the ActiveX visual Interface construction services for

the controls, as well as the function mapping services to the new Interop.MSMAPI32.DLL.

Having found these controls on their upgraded applications, many developers also want to add them to other

VB.NET projects so they can take advantage of them there, but they cannot seem to find a way to easily

access the new DLLs from those new projects. It is doable, but it requires numerous coding hacks.

But relax. Why not just add these two VB6 controls to your VB.NET Toolbox and access them directly? 1. With any form up on the Visual Studio screen so that the IDE toolbox is active, right-click a toolbox category you want to add the

MAPI controls to (if you want to add them to their own category, such as to one named COM, right-click any category and select the Add Tab option, then type the name of your category, such as COM, press ENTER, then right-click that tab).

2. Select the Choose Items… option, and wait for the IDE to build a massive control reference list from the computer. 3. Once the Choose Toolbox Items dialog is finally displayed – select the COM Components tab. 4. Scroll down and put checkmarks in the check boxes for Microsoft MAPI Messages Control, Version 6.0, and Microsoft MAPI

Sessions Control, Version 6.0. (Both of these are actually linked to MSMAPI32.OCX, which in turn drilled down to MAPI32.DLL, but they will now both link to a new .NET-compiled axInterop.MSMAPI32.DLL, which then drills down to Interop.MSMAPI32.DLL).

5. Click the OK button, and you will find these two controls now in your selected Toolbox category list, and you can begin using these controls just exactly as you would had been using them under VB6.

Page 3: Send (SMTP) and Retrieve (POP3) Email with Ease under VB.NET

Page –3–

NOTE: If you do not find these entries in the Choose Toolbox Items dialog box, then you may not or no longer have the VB6 redistributables on your system, so you will have to minimally install the free Runtime Distribution Pack for Service Pack 6 for Visual Basic 6.0, available from Microsoft

(www.microsoft.com/downloads/details.aspx?FamilyId=7B9BA261-7A9C-43E7-9117-F673077FFB3C&displaylang=en). You are allowed to do this even if you no longer own VB6. You should also install the Microsoft Visual Basic 6.0 Service Pack 6 Cumulative Update (www.microsoft.com/download/en/details.aspx?amp;displaylang=en&id=7030).

PART ONE

Sending Email under VB.NET using Native Methods

VB.NET has its own Outbound SMTP Email class that supports sending email, and without a need to

add the more resource-hungry form controls, as we had to do with VB6. Because this technique is more

accessible than supporting inbound email under VB.NET, we will first look at sending email out.

Some people think that you simply hit a system-linked “Send” button and a message they had just typed

is automatically launched into the labyrinths of the internet with possibly little or no code from you.

Were that it be so easy. But hopefully you will now be able to make your clients think you made it so.

Back in the “old days” of software engineering, say the early 1990s, we processed email through a thing

called a Berkeley Socket (circa 1983). This “socket” simply described the endpoint of a bidirectional

inter-process communication flow across an Internet Protocol-based network. It was sometimes a real

trick to program for, depending on the platform, but when it functioned correctly, it was a work of art.

In a pinch we launched a TelNet client and manually typed the various commands

to log on to an email server, send, receive, and read email, and finally disconnect.

Those were cryptic and unforgiving days. But looking back to those times, I have to

wonder if we were either brilliant geniuses or major drool-monkeys, because we

thought back in those younger and smarter years that it was all simple child’s play.

Quick and Dirty Email Senders

Nowadays, we have built-in tools to do most of the hard stuff for us, such as the System.Net Namespace.

This class library provides the .NET SMTP Outbound Mail class. To use it, in the heading of your form or

module, above the class declaration of the file you want to implement it in, I will ask you to add this line:

Imports System.Net, System.Text, VB = Microsoft.VisualBasic 'Most the code in this article REQUIRES this Imports line!

NOTE: Some people import System.NET.Mail just to avoid typing “Mail.” later in their code, but we will also need access to the System.Net.Mime namespace, and even later the System.Net.Sockets namespace. Note that the third part of the line, the declaration of VB, is not really necessary, but BOY is it handy to have in most every class or module we write.

NOTE: MIME (or Mime) is an anagram for Multipurpose Internet Mail Extensions.

First, if you want to send a fast note to someone, most servers will allow you to use the following method:

'******************************************************************************* ' Function Name : BrainDeadSimpleEmailSend ' Purpose : Send super simple email message (works with most SMTP servers) '=============================================================================== 'NOTES: strFrom : Full email address of who is sending the email. ie, David Dingus <[email protected]> ' strTo : Full email address of who to send the email to. ie, "Bubba Dingus" <[email protected]> ' strSubject: Brief text regarding what the email concerns. ' strBody : text that comprises the message body of the email. ' smtpHost : This is the email host you are using for sending emails, such ' : as "smtp.comcast.net", "authsmtp.juno.com", etc. '******************************************************************************* Public Sub BrainDeadSimpleEmailSend(ByVal strFrom As String, _ ByVal strTo As String, _ ByVal strSubject As String, _ ByVal strBody As String, _ ByVal smtpHost As String) Dim smtpEmail As New Mail.SmtpClient(smtpHost) 'create new SMTP client using TCP port 25 smtpEmail.Send(strFrom, strTo, strSubject, strBody) 'send email End Sub

NOTE: The FROM and TO email addresses can be simple, such as [email protected], or more “trendy” formats, such as “Bernard Shaw Fullo” <[email protected]> or even Coat Mahatma <[email protected]>. If the data contains angle brackets, the Mail object will use only the data contained within them. If there are no angle brackets, then the mail object will surround the data with angle brackets, assuming that the entire text is an email address.

Page 4: Send (SMTP) and Retrieve (POP3) Email with Ease under VB.NET

Page –4–

NOTE: Yahoo, Gmail, HotMail, and Juno are internet-based services providing both internet and SMTP/POP3 access. Unlike the other three, Yahoo, normally free, requires an additional monthly fee for SMTP/POP3 access. Juno and HotMail provide this service freely to their subscribers. Gmail, a free service, provides it if you set an option in the POP Download section of the Forwarding and POP/IMAP option within its internet account Settings. You will, of course, have to set up SMTP/POP3 accounts and access within your favorite local email application for all four, such as Windows Mail, Outlook Express, or Outlook.

The above method actually works for most SMTP servers. For example, Comcast and Juno both support

this interface. I use this for quick messages (though they can also be major literary works), like most

people post text messages on their cell phones. However, I think it would be a bit difficult to drive down

the road with a desktop PC and keyboard in hand, trying to steer while I thumb a quick message.

NOTE: Texting while driving is illegal here in Florida, as it should be. In 2010 I witnessed 6 accidents and 1 fatality due to driver texting, primarily by young people, though I must concede that they could have been more distracted by their stereo systems blasting so loudly that it made both their eyes bounce from one side of their head to the other, impairing their vision.

The SMTP Host is the address of your email provider’s SMTP server. SMTP is a TCP/IP (Transmission Control Protocol/Internet Protocol) process used for sending and receiving email. However, because

SMTP is limited in its capability to queue messages at its receiving end, it is typically used with one of

two other protocols, like POP3 or IMAP (Internet Message Access Protocol). But that is a topic we will

cover after we resolve the email sending issues that many thousands of developers are presently having.

For a much more robust method that supports most-all servers, including those that use security layers,

like Gmail, you might try the following method to send a quick email with no attachments:

'******************************************************************************* ' Function Name : QuickiEMail ' Purpose : Send a simple email message (but packed with a lot of muscle) '=============================================================================== 'NOTES: strFrom : Full email address of who is sending the email. ie, David Dingus <[email protected]> ' strTo : Full email address of who to send the email to. ie, "Bubba Dingus" <[email protected]> ' strSubject: Brief text regarding what the email concerns. ' strBody : text that comprises the message body of the email. ' smtpHost : This is the email host you are using for sending emails, such ' : as "smtp.gmail.com", "smtp.comcast.net", "authsmtp.juno.com", etc. ' smtpPort : TCP Communications Port to use. Most servers default to 25, though 465 (SSL) or 587 (TLS) are becoming popular. ' usesSLL : If this value is TRUE, then use SSL/TLS Authentication protocol for secure communications. ' SSLUsername: If usesSLL is True, this is the username to use for creating a credential. Leave blank if the same as strFrom. ' SSLPassword: If usesSLL is True, this is the password to use for creating a credential. If this field and SSLUsername ' : are blank, then default credentials will be used (only works on local, intranet servers). ' SSLDomain : If creating a credential when a specific domain is required, set this parameter, otherwise, leave it blank. '******************************************************************************* Public Function QuickiEMail(ByVal strFrom As String, _ ByVal strTo As String, _ ByVal strSubject As String, _ ByVal strBody As String, _ ByVal smtpHost As String, _ Optional ByVal smtpPort As Integer = 25, _ Optional ByVal usesSSL As Boolean = False, _ Optional ByVal SSLUsername As String = vbNullString, _ Optional ByVal SSLPassword As String = vbNullString, _ Optional ByVal SSLDomain As String = vbNullString) As Boolean Try Dim smtpEmail As New Mail.SmtpClient(smtpHost, smtpPort) 'create new SMTP client smtpEmail.EnableSsl = usesSSL 'true if SSL Authentication required If usesSSL Then 'SSL authentication required? If Len(SSLUsername) = 0 AndAlso Len(SSLPassword) = 0 Then 'if both SSLUsername and SSLPassword are blank... smtpEmail.UseDefaultCredentials = True 'use default credentials Else 'otherwise, we must create a new credential If Not CBool(Len(SSLUsername)) Then 'if SSLUsername is blank, use strFrom smtpEmail.Credentials = New NetworkCredential(strFrom, SSLPassword, SSLDomain) Else smtpEmail.Credentials = New NetworkCredential(SSLUsername, SSLPassword, SSLDomain) End If End If End If smtpEmail.Send(strFrom, strTo, strSubject, strBody) 'send email using text/plain content type and QuotedPrintable encoding Catch e As Exception 'if error, report it MsgBox(e.Message, MsgBoxStyle.OkOnly Or MsgBoxStyle.Exclamation, "Mail Send Error") Return False 'return a failure flag End Try Return True 'if no error, then return a success flag End Function

With the QuickiEMail() method, you can send full, plain text message to one recipient with no

attachments, hence; quick and dirty. With this method, you supply it with the full email address for

whom the email is from (you, for example), you also provide the full email address of the person you are

sending it to, a subject for the email (what it is concerning), the body of the text message, and the SMTP

host (such as smtp.comcast.net, authsmtp.juno.com, or smtp.gmail.com).

Page 5: Send (SMTP) and Retrieve (POP3) Email with Ease under VB.NET

Page –5–

If you must use a TCP Port other than Port 25, such as Gmail requires (using Secure TCP Port 587), then

include the needed port number. Also, if you will be using a secure TCP Port, then you will also require

SSL Authentication, so include Yes for the Boolean usesSSL flag. By setting usesSSL to True, this will in

turn mean that you will need to also supply credentials. Do not panic. This is easy. Just look below.

TCP Ports, SSL Authentication, and Creating Credentials By default, most Outbound Servers use TCP Port 25, and they use TCP Port 110 for their Inbound

Server, providing unencrypted, non-secure email transactions. However, some may differ, such as

Gmail, as mentioned above, which uses secure TCP Port 587 for their Outbound Server and TCP Port

995 for their Inbound Server. But this is primarily because Gmail, and some others, requires an SSL

(Secure Socket Layer) to process mail. In these cases you will need to set the optional parameter to that

TCP Port, such as in the following hard-coded example:

Dim smtpEmail As New Mail.SmtpClient("smtp.EngulfNDevour.com", 465) 'Create new SMTP client using Secure TCP Port 465. smtpEmail.EnableSsl = True 'True if SSL authentication required. SmtpEmail.UseDefaultCredentials = True 'Typical for NTLM, negotiate, and Kerberos-based authentication.

NOTE: Yes, yes, I know; TCP port 587 uses a newer breed of security called TLS (Transport Layer Security). But first, it is still an SSL. Second, SSL technology provides identical security. And third, you are not going to find an EnableTls property in the Mail object. Refer to www.sans.org/reading_room/whitepapers/protocols/ssl-tls-beginners-guide_1029.

The above DefaultCredentials property represents the system credentials for the current security context

in which the application is running. For a client application, these are usually the Windows credentials

(username, password, and domain) of the user running the application (for ASP.NET applications, the

default credentials are the user credentials of the logged-in user, or the user being impersonated).

NOTE: To examine your default credentials, access the System.Net.CredentialCache.DefaultNetworkCredentials property.

However, setting the UseDefaultCredentials property to True will apply only to a Microsoft NT LAN Manager (NTLM) using intranet-based Negotiate authentication and Kerberos-based authentication. All

others will have to create a credential. This is no big whoop, as you are about to see.

NOTE: To make sure that IIS supports both the Kerberos protocol and the NTLM protocol, you must confirm that the Negotiate security header is set in the NTAuthenticationProviders metabase property. For Negotiate authentication to function correctly, several exchanges must take place on the same connection. Therefore, Negotiate authentication cannot be used if an intervening proxy does not support keep-alive connections. If this is not understood, it should not concern you.

NOTE: The centralized account management supported by NT Active Directory Services requires a corresponding authentication protocol for network log-on. Based on RFC 1510 (www.ietf.org/rfc/rfc1510.txt), the Kerberos protocol provides enhanced authentication for the distributed computing environment and standardization to interoperate with other operating systems.

FUNNY DIGRESSION: the Term NT stands for New Technology. It was adopted when Microsoft and IBM parted on their joint OS2 venture. IBM, slow about everything (a self-study showed it took them 9 weeks to ship an empty box), they refused to adopt the revolutionary, advanced technology Microsoft was quickly developing without it being time-tested (meaning proven; this is why NASA uses 20-year-old technology), so Microsoft made its own version of OS2 that used it, naming it NT. Now, think about how many times you have read or heard even Microsoft mentioning the term NT Technology?

If you are not accessing a local IIS (intranet) SMTP server, you will need to supply a new credential for

an SSL SMTP client through a new NetworkCredential Object with a Username and Password provided

to it (if Domains differ, then that must be supplied as well), as shown in this hand-coded example:

Dim smtpEmail As New Mail.SmtpClient("smtp.gmail.com", 587) 'create new Gmail SMTP client with SSL (actually TLS) for outgoing eMail smtpEmail.EnableSsl = True 'True if SSL/TLS authentication required smtpEmail.Credentials = New NetworkCredential("[email protected]","MomBNipp0neze") 'new credential with Username, Password

NOTE: You cannot use decorated usernames for creating a network credential. You will not be able to use something like Tukool Firwurds <[email protected]>. You would have to provide just the actual email address: [email protected].

Although the above methods work in most domains, highly secure domains may require more than an

SSL certificate to reach the outside world. For example, if you are a minion at the Engulf & Devour Credit Corp., you may need to apply code that bypasses massive firewalls and multi-layer proxies,

which any high school youth worth their salt can usually break through before Second Period.

Page 6: Send (SMTP) and Retrieve (POP3) Email with Ease under VB.NET

Page –6–

As indicated, you need to create a NetworkCredential object if you access a server through an SSL/TLS

layer, such as Gmail or HotMail, because you will not be able to use default credentials. Compare these

examples, demonstrating default access through “plain” and TLS secure Comcast servers:

QuickiEMail("[email protected]", "[email protected]", "Letter to the Editor", "Your paper lines my dog cages.", "smtp.comcast.net") 'plain QuickiEMail("Bob <[email protected]>", "[email protected]", "Ltr 2 Ed", "Yr ppr lns m dg cgz 2.", "smtp.comcast.net", 587, True, "Idjut", "pSSwd#6") 'secure

NOTE: Gmail, like a few other TLS servers, require the user’s FULL email address for their certificates (includes @gmail.com). Leaving the SSLUsername field blank, the user’s email address (as long as it is not decorated) will be used.

But even so, just the above QuickiEMail() method supports most emails that people need to transmit,

and is, in fact, all the outgoing email support than a great deal of people will ever require.

An Email Sender with Some Muscle If you may have multiple recipients, multiple optional BCC (Blind Carbon Copy) recipients, multiple

optional CC (Carbon Copy) recipients, multiple attachments, or if you want to send the body text as

HTML format, or send alternate views of the message body, you will require a method with a whole lot

more muscle, like the following SendEmail() method:

'******************************************************************************* ' Function Name : SendEMail ' Purpose : Send a more complex email message '=============================================================================== 'NOTES: strFrom : Full email address of who is sending the email. ie, "David Dingus <[email protected]>" ' strTo : Full email address of who to send the email to. ie, "Bubba Dingus <[email protected]>" ' : If multiple recipients, separate each full email address using a semicolon (;) ' strSubject: Brief text regarding what the email concerns. ' strBody : text that comprises the message body of the email. May be raw text or HTML code ' IsHTML : True if the strBody data is HTML, or the type of data that would be contained within an HTML Body block. ' smtpHost : This is the email host you are using for sending emails, such ' : as "smtp.gmail.com", "smtp.comcast.net", "authsmtp.juno.com", etc. ' AltView : A System.Net.Mail.AlternateView object, such as Rich Text or HTML. ' : If need be, set AltView.ContentType.MediaType and AltView.TransferEncoding to properly format the AlternateView. ' : For example: AltView.ContentType.MediaType = Mime.MediaTypeNames.Text.Rtf ' : AltView.TransferEncoding = Mime.TransferEncoding.SevenBit ' StrCC : Send "carbon copies" of email to this or these recipients. ' : If multiple recipients, separate each full email address using a semicolon (;) ' strBcc : Blind Carbon Copy. Hide this or these recipients from view by others. ' : If multiple recipients, separate each full email address using a semicolon (;) ' strAttachments: A single filepath, or a list of filepaths to send to the recipient. ' : If multiple attachments, separate each filepath using a semicolon (;) (C:\my data\win32.txt; c:\jokes.rtf) ' : The contents of the attachments will be encoded and sent. ' : If you wish to send the attachment by specifying content type (MediaType) and content transfer encoding ' : (Encoding), then follow the attachment name with the MediaType and optional encoding (default is ' : application/octet-stream,Base64) by placing them within parentheses, and separated by a comma. For example: ' : C:\My Files\API32.txt (text/plain, SevenBit); C:\telnet.exe (application/octet-stream, Base64) ' : Where: The MediaType is determined from the System.Net.Mime.MediaTypeNames class, which ' : can specify Application, Image, or Text lists. For example, the above content type, ' : "text\plain", was defined by acquiring System.Net.Mime.MediaTypeNames.Text.Plain. ' : The second parameter, Encoding, is determined by the following the values specified by the ' : System.Net.Mime.TrasperEncoding enumeration: ' : QuotedPrintable (acquired by System.Net.Mime.TransferEncoding.QuotedPrintable.ToString) ' : Base64 (acquired by System.Net.Mime.TransferEncoding.Base64.ToString) ' : SevenBit (acquired by System.Net.Mime.TransferEncoding.SevenBit.ToString) ' smtpPort : TCP Communications Port to use. Most servers default to 25. ' usesSLL : If this value is TRUE, then use SSL Authentication protocol for secure communications. ' SSLUsername: If usesSLL is True, this is the username to use for creating a credential. Leave blank if the same as strFrom. ' SSLPassword: If usesSLL is True, this is the password to use for creating a credential. If this field and SSLUsername ' : are blank, then default credentials will be used (only works on local, intranet servers). ' SSLDomain : If creating a credential with a specific domain is required, set this parameter, otherwise, leave it blank. '******************************************************************************* Public Function SendEMail(ByVal strFrom As String, _ ByVal strTo As String, _ ByVal strSubject As String, _ ByVal strBody As String, _ ByVal IsHTML As Boolean, _ ByVal smtpHost As String, _ Optional ByVal AltView As Mail.AlternateView = Nothing, _ Optional ByVal strCC As String = vbNullString, _ Optional ByVal strBcc As String = vbNullString, _ Optional ByVal strAttachments As String = vbNullString, _ Optional ByVal smtpPort As Integer = 25, _ Optional ByVal usesSSL As Boolean = False, _ Optional ByVal SSLUsername As String = vbNullString, _ Optional ByVal SSLPassword As String = vbNullString, _ Optional ByVal SSLDomain As String = vbNullString) As Boolean Dim Email As New Mail.MailMessage 'create a new mail message With Email .From = New Mail.MailAddress(strFrom) 'add FROM to mail message (must be a Mail Address object) '------------------------------------------- Dim Ary() As String = Split(strTo, ";") 'add TO to mail message (possible list of email addresses; separated each with ";") For Idx As Integer = 0 To UBound(Ary) If Len(Trim(Ary(Idx))) <> 0 Then .To.Add(Trim(Ary(Idx))) 'add each TO recipent (primary recipients) Next '------------------------------------------- .Subject = strSubject 'add SUBJECT text line to mail message '------------------------------------------- .Body = strBody 'add BODY text of email to mail message.

Page 7: Send (SMTP) and Retrieve (POP3) Email with Ease under VB.NET

Page –7–

.IsBodyHtml = IsHTML 'indicate if the message body is actually HTML text. '------------------------------------------- If AltView IsNot Nothing Then 'if an alternate view of plain text message is defined... .AlternateViews.Add(AltView) 'add the alternate view End If '------------------------------------------- If CBool(Len(strCC)) Then 'add CC (Carbon Copy) email addresses to mail message Ary = Split(strCC, ";") '(possible list of email addresses, separated each with ";") For Idx As Integer = 0 To UBound(Ary) If Len(Trim(Ary(Idx))) <> 0 Then .CC.Add(Trim(Ary(Idx))) 'add each recipent Next End If '------------------------------------------- If CBool(Len(strBcc)) Then 'add Bcc (Blind Carbon Copy) email addresses to mail message Ary = Split(strBcc, ";") '(possible list of email addresses; separated each with ";") For Idx As Integer = 0 To UBound(Ary) If Len(Trim(Ary(Idx))) <> 0 Then .Bcc.Add(Trim(Ary(Idx))) 'add each recipent (hidden recipents) Next End If '------------------------------------------- If CBool(Len(strAttachments)) Then 'add any attachments to mail message Ary = Split(strAttachments, ";") '(possible list of file paths, separated each with ";") For Idx As Integer = 0 To UBound(Ary) 'process each attachment Dim attach As String = Trim(Ary(Idx)) 'get attachment data If Len(attach) <> 0 Then 'if an attachment present... Dim I As Integer = InStr(attach, "(") 'check for formatting instructions If CBool(I) Then 'formatting present? Dim Fmt As String 'yes, so set up format cache Fmt = Mid(attach, I + 1, Len(attach) - I - 1) 'get format data attach = Trim(VB.Left(attach, I - 1)) 'strip format data from the attachment path Dim Atch As New Mail.Attachment(attach) 'create a new attachment Dim fmts() As String = Split(Fmt, ",") 'break formatting up For I = 0 To UBound(fmts) 'process each format specification Fmt = Trim(fmts(I)) 'grab a format instruction If CBool(Len(Fmt)) Then 'data defined? Select Case I 'yes, so determine which type of instruction to process Case 0 'index 0 specified MediaType Atch.ContentType.MediaType = Fmt 'set media type to attachment Case 1 'index 1 specifes Encoding Select Case LCase(Fmt) 'check the encoding types and process accordingly Case "quotedprintable", "quoted-printable" Atch.TransferEncoding = Mime.TransferEncoding.QuotedPrintable Case "sevenbit", "7bit" Atch.TransferEncoding = Mime.TransferEncoding.SevenBit Case Else Atch.TransferEncoding = Mime.TransferEncoding.Base64 End Select End Select End If Next .Attachments.Add(Atch) 'add attachment to email Else .Attachments.Add(New Mail.Attachment(attach)) 'add filepath (if no format specified, encoded in effiecient Base64) End If End If Next End If End With '----------------------------------------------------------------------- 'now open the email server... Try Dim SmtpEmail As New Mail.SmtpClient(smtpHost, smtpPort) 'create new SMTP client on the SMTP server SmtpEmail.EnableSsl = usesSSL 'true if SSL Authentication required If usesSSL Then 'SSL authentication required? If Len(SSLUsername) = 0 AndAlso Len(SSLPassword) = 0 Then 'if both SSLUsername and SSLPassword are blank... SmtpEmail.UseDefaultCredentials = True 'use default credentials Else 'otherwise, we must create a new credential If Not CBool(Len(SSLUsername)) Then 'if SSLUsername is blank, use strFrom SmtpEmail.Credentials = New NetworkCredential(strFrom, SSLPassword, SSLDomain) Else SmtpEmail.Credentials = New NetworkCredential(SSLUsername, SSLPassword, SSLDomain) End If End If End If SmtpEmail.Send(Email) 'finally, send the email... Catch e As Exception 'if error, report it MsgBox(e.Message, MsgBoxStyle.OkOnly Or MsgBoxStyle.Exclamation, "Mail Error") Return False 'return failure flag End Try Return True 'return success flag End Function

Notice that in this version we have added more recipients. A lot more. There is a TO list, a Carbon Copy

list (CC), and a Blind Carbon Copy list (BCC; not viewable by TO or CC recipients). The CC list is an

archaic vestige that just forgot to go away. It was essential back in the days when you could specify only

a single email address in the TO field. The BCC was essential for sending copies to other concerned

parties, but it was not essential, or the sender did not want the TO or CC viewers to know that these

BCC recipients were being sent copies. Some claim there is no use for a BCC list, but I beg to differ.

NOTE: Microsoft Mail features BCC, but it does not seem to work (under Vista, anyway).

Page 8: Send (SMTP) and Retrieve (POP3) Email with Ease under VB.NET

Page –8–

The important thing to notice about these three recipient fields is that you separate each recipient with a

semicolon (;). Notice also that in my code that I did a check on each after I split them into an array to

ensure that a field was not empty. Most email applications simply slap a semicolon on the tail of each

email address, so splitting them into an array may leave any last array element empty.

The Attachments, strAttachments, we handle just like TO, CC and BCC. The file paths to the

attachments are separated by semicolons. The Mail.Attachments collection object takes care of loading

the actual file data. Attachments are appended to the end of the email. As noted in the comments above

the method, you can also declare the encoding and display formats for an attachment; otherwise they

will default to binary (“application/octet-stream”) and encoded using the Base64 method, which, as all,

converts them to encoded 7-bit text, which all emails must be formatted to for internet processing.

An email is actually a series of bytes (in email lingo, these are octets; 8-bits), formatted as 7-bit ASCII

text (ergo, the 8th bit is never used). As such, even binary attachments are encoded into blocks of ASCII

text, sometimes formatted as ASCII Hexadecimal (hex; “0” through “9”, and “A” through “F”, allowing

for a Base16 numbering system, though this doubles the data size), where each byte is represented by

two 7-bit characters, and each character represents a nibble, or 4 bits (computer engineers must always be hungry). However, many servers now support various types of encoding, like Base64, to better

transport 8-bit/binary data in the 7-bit-only catacombs of the internet (at a cost of the data’s footprint

being about 25% larger). For example, my API32.txt file, which, by its extension, is hopefully a text

file, looks like gobbledygook at the bottom of my email when converted using the default Base64

encoding. Here is a sampling of its beginning:

NOTE: My comments in the SendEmail() method header regarding attachments, where I discuss formatting the attachment to different content types and encoding, we will leave for later, when I actually discovered these solutions.

From: [email protected] To: [email protected] Date: 21 Feb 2011 21:16:00 -0500 Subject: Test Content-Type: multipart/mixed; boundary=--boundary_0_5fbcc36e-0097-412e-bf2b-c4dc5bc543d0 ----boundary_0_5fbcc36e-0097-412e-bf2b-c4dc5bc543d0 Content-Type: text/plain; charset=us-ascii Content-Transfer-Encoding: quoted-printable This is just a test ----boundary_0_5fbcc36e-0097-412e-bf2b-c4dc5bc543d0 Content-Type: application/octet-stream; name=API32.txt The Name parameter identifies this as an attachment (and it was handled as a binary stream) Content-Transfer-Encoding: base64 How the file is encoded (I would have rather had this be text/plain, with 7bit encoding) JyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0t LS0tLS0tLS0tLS0tLS0tLS0tLS0NCicNCicgICAgIEFQSTMyLlRYVCAtLSBXaW4zMiBBUEkg VHlwZSBEZWNsYXJhdGlvbnMgZm9yIFZpc3VhbCBCYXNpYw0KJw0KJyAgICAgICAgICAgICAg ICAgICAgICAgQ29weXJpZ2h0IChDKSAxOTk2IERlc2F3YXJlDQonDQonICBZb3UgaGF2ZSBh IHJveWFsdHktZnJlZSByaWdodCB0byB1c2UsIG1vZGlmeSwgcmVwcm9kdWNlIGFuZCBkaXN0 ... The encoded definition of several thousand more API declarations, structures, and constants continues from here.

NOTE: In the back of my mind, when I encountered this during my initial email tests, I was really wondering about this Base64 Content Transfer Encoding. But, we will return to this when we examine alternate views and attachments, where it will make more sense to us. We will also learn how we can fully exploit it and very easily decode it (and also how to avoid it).

Two other parameters you may have noticed in the SendEmail() method were IsHTML and AltView.

Sending Email Messages as HTML

The IsHTML parameter in the SendEMail() method sets the state of the Mail.MailMessage object’s

Boolean IsBodyHtml flag. If it is set to True, then the SMTP interface will know to set the body text

formatting flag to text/html instead of its usual text/plain. It is actually up to email reader software to use

that information and determine how to present the data. For example, some plain text readers will simply

show the raw data, regardless. However, others will bring up a web interface, such as a WebBrowser

control, envelop the text within an HTML body, if it is not already, and present that to the user.

Page 9: Send (SMTP) and Retrieve (POP3) Email with Ease under VB.NET

Page –9–

This is quite easy to do, but there is a simple test you will need to perform, because some main body

HTML is sent without HTML/BODY tags, though most are. The easiest test is to simply check to see if

“</HTML>” is contained within the message. If not, all you have to do is prepend the text “<HTML><BODY>”

in front of the message, and append the text “</BODY></HTML>” behind it. And that is all! Consider this

test, where String variable Msg is assumed to contain the HTML-formatted text of the message body:

If Not CBool(InStr(1, Msg, "</HTML>", CompareMethod.Text)) Then 'Msg contains an HTML wrapper? Msg = "<HTML><BODY>" & Msg & "</BODY></HTML>" 'no so add one to it End If

Suppose I sent the following urgent code red email message (note the True for the IsHTML parameter):

Dim Msg As String = "<b>This is bold text</b><p><u>This should be underlined</u><p>" 'some simple HTML text SendEMail("[email protected]", "[email protected]", "Test", Msg, True, "smtp.80micro.com")

My Gmail account will receive an email with the following at the bottom of the data (sans my notes):

From: [email protected] To: [email protected] Date: 21 Feb 2011 21:39:04 -0500 Subject: Test Content-Type: text/html; charset=us-ascii Used second to determine how to display or process the data Content-Transfer-Encoding: quoted-printable Used first to determine how to decode the data <b>This is bold text</b><p><u>This should be underlined</u><p> The HTML formatted text without HTML or BODY tags

Once I strip out the header data (I will show this later), I end up with a String variable I named Msg that

contains the text “<b>This is bold text</b><p><u>This should be underlined</u><p>”. I would

expose it within my Web Browser interface control, WebBrowser1, using code similar to the following:

'set the web browser to the same location as my usual text display control Me.WebBrowser1.Bounds = Me.RichTextBox1.Bounds Me.RichTextBox1.Visible = False 'hide my plain text/Richtext textbox If Not CBool(InStr(1, Msg, "</HTML>", CompareMethod.Text)) Then 'Does my Msg contain an HTML wrapper? Msg = "<HTML><BODY>" & Msg & "</BODY></HTML>" 'no so add one to it End If 'set web browser contents to that of my email message body and display it Me.WebBrowser1.DocumentText = Msg Me.WebBrowser1.Visible = True 'expose the web browser

The two lines of text are dutifully displayed using the HTML formatting I assigned to

them. The first line was Bold, and the second line was underlined. The urgent code red

message was processed and read in time! The world is once again safe.

There is so much more to explore if you truly want to create a full-featured email processor.

The Internet Message Format document, RFC 2822 (www.ietf.org/rfc/rfc2822.txt), outlines all the

gory details of email formatting.

I should close this sub-section by saying that the message data on multi-line documents should be

further processed to ensure that the data is properly formatted for your web browser, rich text box, or

simple text box, such as by decoding special tags that may have been added, such as to represent

Unicode text within the simple 7-bit ASCII text format required for email transactions, for example.

Data in an email is a series of lines, each terminated by a vbCrLf (Carriage Return and Linefeed – codes

13 and 10, respectively). Often these line terminators are not a part of the original text, but are required

to limit the line width of the email data. In these cases they are tagged, such as by using a “soft return”

flag, like the equals sign “=” used by Quoted-Printable encoding. These must be decoded and removed

before displaying the text. Also with Quoted-Printable-encoded text, if a space character precedes a

vbCrLf, then that space is converted to a special hexadecimal format, “=20”, which I habitually call

Hex-Tags. Also, if there are any 8-bit characters embedded in the message, then you should convert

them to Hex-Tags, otherwise the system will automatically encode the data to Base64, regardless of

what you really want, such as Quoted-Printable, simply to ensure 100% original data integrity.

Fortunately, I will later present very simple functions to allow Quoted-Printable encoding of 8-bit text

that will convert any 8-bit codes to 7-bit without losing integrity, as well as decode Quoted-Printable-

encoded and Base64-encoded data back to its original form.

Page 10: Send (SMTP) and Retrieve (POP3) Email with Ease under VB.NET

Page –10–

Sending Alternate Message Views Another thing I want to explore with you is one of the more interesting parameters I have listed for the

SendEMail() method, and that is AltView.

The AltView parameter is declared as a System.Net.Mail.AlternativeView object. Even though my

SendEMail() method presently allows for only one alternative view, which is usually all we ever really

need, the .NET SMTP processor will actually allow for as many as you want, in compliance to RFC 2822, as though we would want to spend the rest of our miserable lives toiling over the various

formatting of a single email to say “Thanks for the $1 on my birthday” to Great Aunt Ethel. However,

the real reason for this is more mundane: some people simply want to send both a “pretty” version of

their email and a plain text version for those who may want to view them on a cell phone. Technically,

most email processors will, by default, display the alternate view, or the first alternate view they can

support, leaving the Plain Text version as the “last ditch” option.

Typically, most of us tend to send plain text emails, even if they are in fact

formatted by a Rich Text or HTML editor, failing to add emphasis, bolding, italics,

or underlining. Phooey! I still remember the thrill I got when I hit a button on my

Selectric II Typewriter and it bolded what I typed, or got my first TypeBall – we

called them golf balls – that supported italics. I did not even blink when I had to go

though the time and effort of changing the TypeBall just to change fonts.

For me, at least, plain text is so monotonous and passé.

NOTE: Were I to revert back to the days of no easy bolding, underlining, or italics, I think I would suffer a pulmonary embolism, as may be apparent from reading my document; these features afford us a platform for curt, succinct expression.

Most people prefer their emails to be formatted as HTML or as Rich Text, but they also want the option

to process their text as Plain Text for those who want to read their email on a cell phone, or who are

vision-impaired (“Can’t see out of one eye, and I’m blind as a bat in the other,” as Grandpa often said).

Most of you are aware that in a VB-written Rich Text editor, using a RichTextBox control, you have

easy access to the Plain Text version and the Rich Text version of a document. Its Text property

provides the Plain Text version, and its Rtf property provides the Rich Text version.

But did you know that accessing plain text from HTML formatting can also be easy?

An HTML version can be provided by accessing the DocumentText property of their WebBrowser

control, as most of you are already aware. But a Plain Text rendering has always seemed to be an issue. I

have seen a number of home-spun HTML editors that provide a Plain Text version of their data by

manually stripping out all the HTML Text Tags, plus any special formatting that might be stored within

or between them, on top of going through the often arduous task of interpreting all the special HTML

Entities in order to provide that “simple” plain-text version. This adds up to a whole lot of work.

NOTE: An HTML Text Tag is a thing starting with “<” and ending with “>”, such as “<HTML>” or “</BODY>”.

NOTE: HTML Entities start with an ampersand (&) and end with a semicolon (;), such as “&lt;” to represent an

intentional “<”,“&gt;” for an intentional “>”, “&nbsp;” (non-breaking space) for a blank space “ ” where a space

might normally be ignored, plus “;;” for an intentional “;”. Following is a list of the HTML Reserved Entities:

Character Entity Number Entity Name Description " &#34; &quot; quotation mark (?) ' &#39; &apos; apostrophe (‘) & &#38; &amp; ampersand (&) < &#60; &lt; less-than (<) > &#62; &gt; greater-than (>)

As you can see, there are two versions of tags; one that includes the decimal ASCII code, and one that includes the typical or “classic” representation. The Entity Number also allows 8- or 16-bit extended characters to be displayed by a 7-bit source.

Page 11: Send (SMTP) and Retrieve (POP3) Email with Ease under VB.NET

Page –11–

Even though I have seen a number of utilities, both commercial and shareware, that offer this kind of

service, you can in fact bypass them and remove better than 75% of that work with this 1-line function:

'******************************************************************************* ' Function Name : QConvertHTML2Text ' Purpose : Short-Form Convert HTML formatted text to plain text ' : ' Returns : Provided a simple HTML source string, it will return a Plain Text ' : string with HTML code removed. '******************************************************************************* Public Function QConvertHTML2Text(ByVal HTMLText As String) As String Return RegularExpressions.Regex.Replace(HTMLText.Replace("&nbsp;", " ").Replace("&quot;", """").Replace("&apos;", _ "'"), "<[^>]*>", "").Replace("&lt;", "<").Replace("&gt;", ">").Replace("&amp;", "&").Replace(";;", ";") End Function

The above function will also remove all the extra data within the HTML tags. Just invoke it like this:

“Dim PlainText As String = QConvertHTML2Text(HtmlText)”.

NOTE: In a pinch, copy from the HTML display of a WebBrowser control, paste it to Notepad, then copy it as plain text, but this does not provide for line formatting, and can sometimes look almost as disorganized as a college dorm room.

However, the following enhanced method does 99% of the work that any commercial package offers:

'******************************************************************************* ' Function Name : ConvertHTML2Text ' Purpose : Convert HTML formatted text to plain text ' : ' Returns : Provided a complex HTML string, it will return a Plain Text string ' : with all HTML codes and formatting removed from it. ' : ' NOTE : Numerous of these conversions will convert the text to 8-bit, ' : though most of these sysmbols will not be encountered in most ' : HTML documents we produce. But regardless of that, if you wish ' : to make this conversion the main body message of an email, you ' : may have to further convert this using ForceQuotedPrintable() ' : to maintain Quoted-Printable encoding and avoid Base64, even ' : though this is typically not an issue. However, some few really ' : primitive email readers, typically those that simply allow you ' : to preview email messages, without fully loading them, will not ' ' know how to support Base64, or will not bother with it, but simply ' : display the raw data. RFC 2045 requires email handlers to support it. '******************************************************************************* Public Function ConvertHTML2Text(ByVal HTMLText As String) As String 'instantiate an initially blank StringBuilder object Dim Sb As New StringBuilder() 'first remove leading whitespace of each line and append the result to the StringBuilder Dim ary() As String = Split(HTMLText, vbCrLf) For Each S As String In ary Sb.Append(S.TrimStart(Chr(9), " "c)) Next 'replace reserved entities (except <, >, and &) Sb.Replace("&quot;", """").Replace("&apos;", "'").Replace("&nbsp;", " ") 'replace HTML paragraph, line breaks, and table entry terminators with vbCrLf Sb.Replace("<p>", vbCrLf).Replace("<P>", vbCrLf).Replace("</p>", vbCrLf).Replace("</P>", vbCrLf).Replace("<br>", _ vbCrLf).Replace("<BR>", vbCrLf).Replace("</td>", vbCrLf).Replace("</TD>", vbCrLf) 'replace ISO 8859-1 Symbols (160-255). Note that any matches will make the text 8-bit Sb.Replace("&iexcl;", "¡").Replace("&cent;", "¢").Replace("&pound;", "£").Replace("&curren;", _ "¤").Replace("&yen;", "¥").Replace("&brvbar;", "¦").Replace("&sect;", "§").Replace("&uml;", _ "¨").Replace("&copy;", "©").Replace("&ordf;", "ª").Replace("&laquo;", "«").Replace("&not;", _ "¬").Replace("&shy;", "-").Replace("&reg;", "®").Replace("&macr;", "¯").Replace("&deg;", _ "°").Replace("&plusmn;", "±").Replace("&sup2;", "²").Replace("&sup3;", "³").Replace("&acute;", _ "´").Replace("&micro;", "µ").Replace("&para;", "¶").Replace("&middot;", "•").Replace("&cedil;", _ "¸").Replace("&sup1;", "¹").Replace("&ordm;", "º").Replace("&raquo;", "»").Replace("&frac14;", _ "¼").Replace("&frac12;", "½").Replace("&frac34;", "¾").Replace("&iquest;", "¿").Replace("&times;", _ "×").Replace("&divide;", "÷") 'replace ISO 8859-1 characters. Note that any matches will make the text 8-bit Sb.Replace("&Agrave;", "À").Replace("&Aacute;", "Á").Replace("&Acirc;", "Â").Replace("&Atilde;", "Ã").Replace("&Auml;", _ "Ä").Replace("&Aring;", "Å").Replace("&AElig;", "Æ").Replace("&Ccedil;", "Ç").Replace("&Egrave;", _ "È").Replace("&Eacute;", "É").Replace("&Ecirc;", "Ê").Replace("&Euml;", "Ë").Replace("&Igrave;", _ "Ì").Replace("&Iacute;", "Í").Replace("&Icirc;", "Î").Replace("&Iuml;", "Ï").Replace("&ETH;", _ "Ð").Replace("&Ntilde;", "Ñ").Replace("&Ograve;", "Ò").Replace("&Oacute;", "Ó").Replace("&Ocirc;", _ "Ô").Replace("&Otilde;", "Õ").Replace("&Ouml;", "Ö").Replace("&Oslash;", "Ø").Replace("&Ugrave;", _ "Ù").Replace("&Uacute;", "Ú").Replace("&Ucirc;", "Û").Replace("&Uuml;", "Ü").Replace("&Yacute;", _ "Ý").Replace("&THORN;", "Þ").Replace("&szlig;", "ß").Replace("&agrave;", "à").Replace("&aacute;", _ "á").Replace("&acirc;", "â").Replace("&atilde;", "ã").Replace("&auml;", "ä").Replace("&aring;", _ "å").Replace("&aelig;", "æ").Replace("&ccedil;", "ç").Replace("&egrave;", "è").Replace("&eacute;", _ "é").Replace("&ecirc;", "ê").Replace("&euml;", "ë").Replace("&igrave;", "ì").Replace("&iacute;", _ "í").Replace("&icirc;", "î").Replace("&iuml;", "ï").Replace("&eth;", "ð").Replace("&ntilde;", _ "ñ").Replace("&ograve;", "ò").Replace("&oacute;", "ó").Replace("&ocirc;", "ô").Replace("&otilde;", _ "õ").Replace("&ouml;", "ö").Replace("&oslash;", "ø").Replace("&ugrave;", "ù").Replace("&uacute;", _ "ú").Replace("&ucirc;", "û").Replace("&uuml;", "ü").Replace("&yacute;", "ý").Replace("&thorn;", _ "þ").Replace("&yuml;", "ÿ") 'replace Math Symbols Supported by HTML. Note that any matches will make the text 8-bit Sb.Replace("&forall;", "∀∀∀∀").Replace("&part;", "∂").Replace("&exist;", "∃∃∃∃").Replace("&empty;", "∅∅∅∅").Replace("&nabla;", _ "∇∇∇∇").Replace("&isin;", "∈∈∈∈").Replace("&notin;", "∉∉∉∉").Replace("&ni;", "∋∋∋∋").Replace("&prod;", _ "∏").Replace("&sum;", "∑").Replace("&minus;", "−").Replace("&lowast;", "∗∗∗∗").Replace("&radic;", _ "√").Replace("&prop;", "∝∝∝∝").Replace("&infin;", "∞").Replace("&ang;", "∠∠∠∠").Replace("&and;", _ "∧∧∧∧").Replace("&or;", "∨∨∨∨").Replace("&cap;", "∩").Replace("&cup;", "∪∪∪∪").Replace("&int;", _

Page 12: Send (SMTP) and Retrieve (POP3) Email with Ease under VB.NET

Page –12–

"∫").Replace("&there4;", "∴∴∴∴").Replace("&sim;", "∼∼∼∼").Replace("&cong;", "≅≅≅≅").Replace("&asymp;", _ "≈").Replace("&ne;", "≠").Replace("&equiv;", "≡").Replace("&le;", "≤").Replace("&ge;", _ "≥").Replace("&sub;", "⊂⊂⊂⊂").Replace("&sup;", "⊃⊃⊃⊃").Replace("&nsub;", "⊄⊄⊄⊄").Replace("&sube;", _ "⊆⊆⊆⊆").Replace("&supe;", "⊇⊇⊇⊇").Replace("&oplus;", "⊕⊕⊕⊕").Replace("&otimes;", "⊗⊗⊗⊗").Replace("&perp;", _ "⊥⊥⊥⊥").Replace("&sdot;", "⋅⋅⋅⋅") 'replace Greek Letters Supported by HTML. Note that any matches will make the text 8-bit Sb.Replace("&Alpha;", "Α").Replace("&Beta;", "Β").Replace("&Gamma;", "Γ").Replace("&Delta;", "∆").Replace("&Epsilon;", _ "Ε").Replace("&Zeta;", "Ζ").Replace("&Eta;", "Η").Replace("&Theta;", "Θ").Replace("&Iota;", _ "Ι").Replace("&Kappa;", "Κ").Replace("&Lambda;", "Λ").Replace("&Mu;", "Μ").Replace("&Nu;", _ "Ν").Replace("&Xi;", "Ξ").Replace("&Omicron;", "Ο").Replace("&Pi;", "Π").Replace("&Rho;", _ "Ρ").Replace("&Sigma;", "Σ").Replace("&Tau;", "Τ").Replace("&Upsilon;", "Υ").Replace("&Phi;", _ "Φ").Replace("&Chi;", "Χ").Replace("&Psi;", "Ψ").Replace("&Omega;", "Ω").Replace("&alpha;", _ "α").Replace("&beta;", "β").Replace("&gamma;", "γ").Replace("&delta;", "δ").Replace("&epsilon;", _ "ε").Replace("&zeta;", "ζ").Replace("&eta;", "η").Replace("&theta;", "θ").Replace("&iota;", _ "ι").Replace("&kappa;", "κ").Replace("&lambda;", "λ").Replace("&mu;", "µ").Replace("&nu;", _ "ν").Replace("&xi;", "ξ").Replace("&omicron;", "ο").Replace("&pi;", "π").Replace("&rho;", _ "ρ").Replace("&sigmaf;", "ς").Replace("&sigma;", "σ").Replace("&tau;", "τ").Replace("&upsilon;", _ "υ").Replace("&phi;", "φ").Replace("&chi;", "χ").Replace("&psi;", "ψ").Replace("&omega;", _ "ω").Replace("&thetasym;", "ϑ").Replace("&upsih;", "ϒ").Replace("&piv;", "ϖ") 'replace Other Entities Supported by HTML. Note that any matches will make the text 8-bit Sb.Replace("&OElig;", "Œ").Replace("&oelig;", "œ").Replace("&Scaron;", "Š").Replace("&scaron;", "š").Replace("&Yuml;", _ "Ÿ").Replace("&fnof;", "ƒ").Replace("&circ;", "ˆ").Replace("&tilde;", "˜").Replace("&ensp;", _ " ").Replace("&emsp;", " ").Replace("&thinsp;", "    ").Replace("&ndash;", "–").Replace("&mdash;", _ "—").Replace("&lsquo;", "‘").Replace("&rsquo;", "’").Replace("&sbquo;", "‚").Replace("&ldquo;", _ " ").Replace("&rdquo;", " ").Replace("&bdquo;", "„").Replace("&dagger;", "†").Replace("&Dagger;", _ "‡").Replace("&bull;", "•").Replace("&hellip;", "…").Replace("&permil;", "‰").Replace("&prime;", _ "′").Replace("&Prime;", "″").Replace("&lsaquo;", "‹").Replace("&rsaquo;", "›").Replace("&oline;", _ "").Replace("&euro;", "€").Replace("&trade;", "™").Replace("&larr;", "←").Replace("&uarr;", _ "↑").Replace("&rarr;", "→").Replace("&darr;", "↓").Replace("&harr;", "↔").Replace("&crarr;", _

"↵↵↵↵").Replace("&lceil;", "⌈⌈⌈⌈").Replace("&rceil;", "⌉⌉⌉⌉").Replace("&lfloor;", "⌊⌊⌊⌊").Replace("&rfloor;", _

"⌋⌋⌋⌋").Replace("&loz;", "◊").Replace("&spades;", "♠").Replace("&clubs;", "♣").Replace("&hearts;", _ "♥").Replace("&diams;", "♦") 'replace special ASCII coding entities that were not captured by the above. Note that values > 127 will make the text 8-bit For Idx As Integer = 1 To 255 'See www.w3schools.com/tags/ref_entities.asp Sb.Replace("&#" & Idx.ToString & ";", Chr(Idx)) 'replace most common numeric entities Next 'Ensure header definitions are followed by vbCrLf Dim NewText As String = RegularExpressions.Regex.Replace(Sb.ToString(), "</H[^>]*>", vbCrLf) 'Also seek out other Unicode encoded number entities not covered by the above and individually update them Dim Idy As Integer = InStr(NewText, "&#") 'check for a numeric entity Do While Idy <> 0 'loop as long as we find one Dim Idz As Integer = InStr(Idy, NewText, ";") 'find terminating semicolon Dim S As String = Mid(NewText, Idy, Idz - Idy + 1) 'grab expression RegularExpressions.Regex.Replace(NewText, S, Chr(CInt(Mid(S, 3, Len(S) - 3)))) 'replace expression InStr(Idy + 1, NewText, "&#") Loop 'strip remaining HTML text tags, replace < and > placeholders, convert ampersand, replace ;; with ;, then return result Return RegularExpressions.Regex.Replace(NewText, "<[^>]*>", "").Replace("&lt;", _ "<").Replace("&gt;", ">").Replace("&amp;", "&").Replace(";;", ";") End Function

Page 13: Send (SMTP) and Retrieve (POP3) Email with Ease under VB.NET

Page –13–

Suppose you wanted to send both A Rich Text and a Plain Text version of an email to someone, so if

they were trendy and wanted to view the chic version of your email as Rich Text, they could. Or, if they

were viewing your email on their trendy Blackberry, Droid, iPhone, or other small Email-enabled

portable device, they could read your email in plain text, which reads much better on small screens.

You typically pass the Plain Text version as the strBody parameter of the SendEMail() method. The

Rich Text or HTML version would be sent as an alternate view.

Email is progressing to the point to where most emails are in HTML format, which is why we have the

IsBodyHtml parameter on a MailMessage object, allowing us to transmit the main message body as

HTML. However, Rich Text is becoming so common that I expect one day to see an industry-

recognized IsBodyRtf parameter, though I would really hope that the main body would be forced to Plain

Text, and IsHtmlBody be revoked. Or, to be diplomatic, simply allow the user to specify the Content-Type and Content-Transfer-Encoding of the message body they choose to send.

Consider the following, where early on in my trials I tried to send an email with a RTF alternate view:

'create an alternate view of the RTF data from our RichTextBox Control Dim rtfView As Mail.AlternateView = Mail.AlternateView.CreateAlternateViewFromString(Me.RichTextBox1.Rtf) 'send our email as plain text and with a rich text alternater view SendEMail("[email protected]", "[email protected]", "Test", Me.RichTextBox1.Text, False, "smtp.80micro.com", rtfView)

As you see, creating an AlternateView object seems almost too easy. You can see it using the Rich Text

data for the alternate view, and the plain text for the message body parameter.

But look at my result:

From: [email protected] To: [email protected] Date: 22 Feb 2011 17:19:08 -0500 Subject: Test Content-Type: multipart/alternative; This email is multi-part (either alternate view or attachments added) boundary=--boundary_0_253a0455-dce6-4622-b262-ae5f1cfc0321 Declare unique boundary: will be used to mark the boundaries of each part ----boundary_0_253a0455-dce6-4622-b262-ae5f1cfc0321 First REAL boundary marker. Main body of email follows (usually plain text) Content-Type: text/plain; charset=us-ascii Used as clue toward how to process data after it is decoded Content-Transfer-Encoding: quoted-printable Encoded as Quoted-Printable. All controls codes rendered visible (=xx) This first blank line is NOT part of the message body (MUST BE BLANK) This is a test of Rich Text data.=0A Note the decoration (=0A); a necessary result of email. Easy to address. ----boundary_0_253a0455-dce6-4622-b262-ae5f1cfc0321 End boundary for main body, as well as start boundary for next part Content-Type: text/plain; charset=utf-8 This reports plain text using UTF-8 (8-bit Unicode) Content-Transfer-Encoding: base64 Encoded using Base64 method This first blank line is NOT part of the attachment (MUST BE BLANK) e1xydGYxXGFuc2lcYW5zaWNwZzEyNTJcZGVmZjBcZGVmbGFuZzEwMzN7XGZvbnR0Ymx7XGYw Base64-encoded data (is this rich text?.......Yes, it is, but encoded. XGZuaWxcZmNoYXJzZXQwIFRpbWVzIE5ldyBSb21hbjt9e1xmMVxmbmlsXGZjaGFyc2V0MCBN When it is decoded, it will be able to be processed as rich text) aWNyb3NvZnQgU2FucyBTZXJpZjt9fQ0Ke1xjb2xvcnRibCA7XHJlZDBcZ3JlZW4wXGJsdWUw O30NClx2aWV3a2luZDRcdWMxXHBhcmRcY2YxXGYwXGZzMjQgVGhpcyBpcyBhIFxiIHRlc3Qg XGIwIG9mIFxpIFJpY2ggVGV4dCBkYXRhXGkwIC5ccGFyDQpccGFyZFxjZjBcZjFcZnMxN1xw YXINCn0NCg== ----boundary_0_253a0455-dce6-4622-b262-ae5f1cfc0321-- End boundary for second part. Side note: note the extra "--" on both sides

What the–? (Well, that is what I first said. The notes tend to indicate garnered knowledge since then.)

NOTE: Even though I did not realize it at the time I first saw this, I can in fact fully use and properly translate the above alternate view data with unbelievable ease. But that is getting ahead of what I had thought I was trying to do…

Sending Alternate Message Views with Different Context Types and Transfer Encoding There’s that Base64 thing again. Also, my Rich Text version of the message looks like the cat was

dancing on the keyboard chasing the fish on the computer’s screen saver. But for this implementation,

the MSDN documentation states in its remarks: “The default media type is plain text and the default encoding is ASCII.” Well, that’s kinda true, except that UTF-8 (8-bit Unicode Transformation Format) is not actually ASCII (Standard ASCII, also known as simply ASCII, is strictly 7-bit code). Microsoft’s

version of “ASCII” it is a transformation format for translating between 16-bit Unicode and 8-bit

Extended-ASCII code (UTF-8). And there is also that Base64 part. Something smells fishy, and it’s not

the fish on the screen saver.

In my more rational mind, I acknowledged that the first part, the plain text version, looks fine. But the

Rich Text version is tagged, according to the Content-Type, as plain text, UTF-8. Yet the Content-Transfer-Encoding indicates Base64. I know I have seen many other Rich Text emails, and they show

Page 14: Send (SMTP) and Retrieve (POP3) Email with Ease under VB.NET

Page –14–

that the Content-Type is “text/richtext”, but with them you can even see the HTML-like Rich Text

encoding of the message body perfectly fine. So instead of shaking my fists at the heavens in forlorn,

unquenchable yearning for success, I decided that it was time to hit the books and find out what I was

not (yet) doing.

Scanning the internet yielded no useful results. I found countless requests for help from people in the

same boat as me (well – 438,000 anyway; a crowded boat), but the knowledgeable respondents, always

trying to be helpful (except for the non-social jerks who just want to make everyone else as miserable as they are), replied with some resigned candor that the hapless inquisitors were stuck with Base64

encoding, and we are presently incapable of changing that harsh truth any time soon under VB.NET.

After more time searching MSDN resources, I found another overload (which, dummy me, I should

have found in the first place), specifying another parameter for the CreateAlternateViewFromString()

method. With it, I could add a second parameter that was a System.Net.Mime.ContentType object.

Hmm. Maybe it was not fish I smelled, after all. Is it maybe bovine scat?

If we look at this ContentType object, we can specify the text that Content-Type reports, such as

“text/plain”, “text/richtext”, or whatever I choose (this would be handy for encrypted emails, where you

could specify text indicating which decryption method for your custom email reader to use).

Constructing a ContentType object is easy. For example:

Dim ContentType As New Mime.ContentType(Mime.MediaTypeNames.Text.RichText)'create an alternate view of the RTF data from our RichTextBox

NOTE: All the code in this article will assume that you have at least imported System.NET. and System.Text.

Now suppose we sent that email again…

'create a custom ContentType object to "specify text/richtext" Dim ContentType As New Mime.ContentType(Mime.MediaTypeNames.Text.RichText)'create an alternate view of the RTF data from our RichTextBox Dim rtfView As Mail.AlternateView = Mail.AlternateView.CreateAlternateViewFromString(Me.RichTextBox1.Rtf, ContentType) 'send our email as plain text and with a rich text alternater view SendEMail("[email protected]", "[email protected]", "Test", Me.RichTextBox1.Text, False, "smtp.80micro.com", rtfView)

D’OH! The results are the same! The only difference between the current version and the previous

version is that instead of the Content-Type declaring “text/plain”, it states “text/richtext”, but the

Content-Transfer-Encoding was still set to Base64 (right now, I’m starting to hate Base64… a lot).

NOTE: Unbeknownst to me at the time, but this just gave me all the information I needed to actually use that data!

But Wait! I did more checking and I realized that there were actually 3 overloads to the

CreateAlternateViewFromString() method (I guess I tend to go blind at 2 o’clock in the morning, even

though I have plenty of time to sleep and still get up at 5 AM). I searched really hard to find MSDN

examples that demonstrated them all (which took some real doing. By the way, all the examples I did find on

MSDN were for C# and C++, as though VB code was totally incapable of doing these simple things. Idjuts).

The third overload did not expect just a Mime parameter, but it expected a System.Text.Encoding class and

then the System.Net.Mime.MediaTypeNames string. (I can already sense a move into the right direction…)

Encoding specifies a series of conversion classes. Usually, we use these classes to convert one type of

text to another. But here, by specifying a method, like System.Text.Encoding.ASCII, we do not use the

method, but rather we supply that method to SMTP, which will in turn use it to convert our data.

Based on the kind of conversion we specify from the Encoding class, the Content-Transfer-Encoding

mechanism (which I had been having issues with) will be internally set to one of 3 values: quoted-

printable, where all control codes are marked with 7-bit Hex-Tags (“=” followed by a 2-character

hexadecimal value, such as “=0D” for a vbCr), base64, where binary or 8-bit code, or text containing 8-

bit code will be compressed into a string of 7-bit text tokens, or 7bit, which is unencoded 7-bit data.

Therefore, if we are passing ASCII data, such as Plain Text, HTML, or Rich Text, it will be encoded as

Quoted-Printable as long as we pass a text-based method from Encoding. If we leave this field blank, or

Page 15: Send (SMTP) and Retrieve (POP3) Email with Ease under VB.NET

Page –15–

the data is not text, or it contains 8-bit text, it can be expected to be processed as Base64. And that is

why we have been getting Base64 as the Content-Transfer-Encoding type all this time on attachments.

NOTE: An AlternateView object is actually embedded within an email as an Attachment. The only difference between the

two is that an AlternateView will not specify a filename parameter in the Content-Type field. For example, “Content-Type: text/plain; charset=us-ascii” versus “Content-Type: text/plain; name="Baby Names.txt"”. Note further that the specified default filename in an Attachment will not be quoted if there are no spaces embedded within it.

MediaTypeNames specifies 3 subclass types of interest, which are most notably useful for regular

attachments: Application, Image, and Text. Each of these in turn has subtypes, which is why you see the

Context-Type field indicate “text/plain” or “application/octet-stream”. The first part is the Type, and the

second part, after the slash, is the Subtype. All this will, again, be discussed in the next section.

Presently, we are most interested in the Text type. But we must remember that this field is only text and

informational to the email application. It does nothing to the data; it simply specifies how the decoded

data should be processed. In fact, if you specify “text/plain”, but the data contains 8-bit characters, you

will find that the encoding method will be forced to Base64, which is actually valid and legal.

NOTE: Rather than depend on using the System.Net.Mime.MediaTypeNames class to determine this Content-Type data, you can simply supply your own string, as long as your recipient’s email software can use that information.

With the above gained knowledge, we will try transmitting our email again:

'create an alternate view of the RTF data from our RichTextBox Control Dim rtfView As Mail.AlternateView = Mail.AlternateView.CreateAlternateViewFromString(Me.RichTextBox1.Rtf, _ System.Text.Encoding.ASCII, _ System.Net.Mime.MediaTypeNames.Text.RichText) 'send our email as plain text and with a rich text alternater view SendEMail("[email protected]", "[email protected]", "Test", Me.RichTextBox1.Text, False, "smtp.80micro.com", rtfView)

And now, as you can see, our email result is inching much closer to my vision of it being perfect:

From: [email protected] To: [email protected] Date: 22 Feb 2011 20:56:39 -0500 Subject: Test Content-Type: multipart/alternative; boundary=--boundary_0_12390f25-c489-470a-a36a-c3eb5dc04ad9 ----boundary_0_12390f25-c489-470a-a36a-c3eb5dc04ad9 Content-Type: text/plain; charset=us-ascii Content-Transfer-Encoding: quoted-printable This is a test of Rich Text data.=0A ----boundary_0_12390f25-c489-470a-a36a-c3eb5dc04ad9 End of message body, start of an attachment block. Content-Type: text/richtext; charset=us-ascii This attachment is an alternate view, because a NAME parameter is not specified. Content-Transfer-Encoding: quoted-printable Because data is encoded Quoted-Printable, you will see the (=) Hex-Tags decorating the following RTF message block. \rtf1\ansi\ansicpg1252\deff0\deflang1033\fonttbl\f0\fnil\fcharset0 Times= Because the text is encoded Quoted-Printable, Hex-Tags have been New Roman;\f1\fnil\fcharset0 Microsoft Sans Serif;=0D=0A\colortbl= added for all control codes to ensure total original media content. ;\red0\green0\blue0;=0D=0A\viewkind4\uc1\pard\cf1\f0\fs24 This is a \b= test \b0 of \i Rich Text data\i0 .\par=0D=0A\pard\cf0\f1\fs17\par=0D=0A= =0D=0A ----boundary_0_12390f25-c489-470a-a36a-c3eb5dc04ad9—

In the above Rich Text data, an equals sign “=”precedes each physical end of line, which must be

removed along with the vbCrLf following it. They are used to mark a ‘soft’ line return, dictated by its

requirements, not by the original text. This equals sign also precedes document special codes (having a

value less than 32) that are rendered to Hex-Tags by being converted to 7-bit Hex format (for example,

“=OD” represents Hex(&HD), or a Carriage Return, and “=0A” represents Hex(&HA), or a Linefeed).

This, and any other hex digits that are formatted as a Hex-Tag, will have to be translated back to their

original codes (the fact that the encoding reported quoted-printable should clue you in to that). You may

also see a lot of “=20” tags, which represents a non-breaking space, inserted in places where it would

otherwise be removed or ignored. Note also that an actual ‘=’ in the text will be, ironically, encoded to

“=3D”; the hex code for “=”.

NOTE: Though Quoted-Printable encoding will change conversion to Base64 if it finds bytes codes greater than 127 (8-bit data), we will later provide a method to encode those 8-bit codes to Hex-Tags, so we can send them Quoted-Printable, or even as 7bit.

Page 16: Send (SMTP) and Retrieve (POP3) Email with Ease under VB.NET

Page –16–

For example, if a string variable named Msg contains the above lines of RTF data, we could filter it to a

more perfect form using the following general “quick-clean” code:

Me.RichTextBox1.Rtf = Msg.Replace("=" & vbCrLf, vbNullString).Replace("=0D", vbCr).Replace("=0A", _ vbLf).Replace("=20", " "c).Replace("=3D", "="c)

Hence, the above RTF data becomes perfectly formatted to its original form:

\rtf1\ansi\ansicpg1252\deff0\deflang1033\fonttbl\f0\fnil\fcharset0 Times New Roman;\f1\fnil\fcharset0 Microsoft Sans Serif; \colortbl ;\red0\green0\blue0; \viewkind4\uc1\pard\cf1\f0\fs24 This is a \b test \b0 of \i Rich Text data\i0 .\par \pard\cf0\f1\fs17\par

But one thing it does not do is take care of other possible hex codes, which we will inevitably encounter.

So I end up where I was, unless I choose to implement code to slog through the data that could easily

chew up a lot of resources while it processes. An example of such an interim solution follows:

Msg = Msg.Replace("=" & vbCrLf, vbNullString) 'clean up line termination tags For idx As Integer = &H1 To &HFF 'process the whole ASCII gambit (=00 never used) Dim Hx As String = Hex(idx) 'convert to hex value If idx < 16 Then Hx = "0" & Hx 'leading zero if less than 16 (1-9, A-F) Msg = Msg.Replace("=" & Hx, Chr(idx)) 'replace hex data with single character code Next Me.RichTextBox1.Rtf = Msg 'stuff result to rich text box

But wait a minute! If you recall from an earlier article, I mentioned something about the StringBuilder

object that was 200 times faster than standard strings. Manipulations use very little resources, and what

resources they do use will get flushed away in a flash when we leave the function. We will explore such

support in our next section, where we will cover E-Z decoding of Quoted-Printable and Base64 data.

However, let us stop what we are doing and simply think about ASCII-type encoding for a moment.

ASCII refers to 7-bit code (128 character codes), even though now we often think of it in terms of 8-bits

(256 codes). But that is all because of the immense presence of the Microsoft Windows environment. On

the internet, though, where server standards must conform to the least-common-denominator, Unix is

King, and many (ancient) versions of Unix use only standard ASCII. If we look under .NET, the ASCII

encoding class actually does support 7-bit ASCII, so some of you may be wondering why your control

codes are being translated to Hex-Tags, when they are supposed to be a part of standard ASCII?

…Because the text is being encoded as Quoted-Printable, and this specification always renders all of its

non-displayable ASCII tokens as displayable Hex-Tags, so not one character of that data is lost.

NOTE: One of the cardinal rules of email processing is that not one byte of a transmitted message can ever be allowed to be lost or altered in such a way that they cannot be fully restored to their original format. Hence, even if you send a plain text message, but the “plain text” contains some 8-bit codes, such as Chr(149), “·”, the text will be forced to being encoded to

Base64, so that the data can be transmitted over the internet as 7-bit ASCII data and can afterward be fully restored.

So what I really need is unencoded 7-bit US-ASCII support. Hmm. I first thought about UTF-7, which is

a 7-bit version of Unicode, but then I realized that the same result would follow, due to Quoted-Printable encoding. However, according my references, the unencoded 7bit option is exactly what I

need.

But how do I get there? It seemed that no matter what I had been trying, I was getting no closer to

attaining my actual goal: sending the rich text alternate view as unencoded rich text data.

I would have to think outside the box I had worked myself into. That was when I considered that an

AlternateView and an Attachment, like everything else in .NET, is an object. And objects seem to always

have a lot of properties. So I took a look at Attachment and AlternateView properties. Eureka! I

instantly discovered two members that were perfect to my purpose: The first was ContextType, and the

other was TransferEncoding (the little hairs on the back of my neck are starting to rise). And even

better, they were both malleable properties (that is, they are properties that we can both read from and

write to). I did not expect that they would be so easy to find, considering everyone who has been

complaining about this on the internet. But the important thing was that I now had exactly what I need.

Page 17: Send (SMTP) and Retrieve (POP3) Email with Ease under VB.NET

Page –17–

After a little exploring, I figured out that all I had to do was set the MediaType string property of

ContextType. I could use System.Net.Mime.MediaTypeNames again to set it. TransferEncoding was an

integer enumeration, offering me a choice of QuotedPrintable (0), Base64 (1), or SevenBit (2). A fourth

option, Unknown, spits only venomous messages of non-support into our eyes, if we try to play with it.

But the good news is – I can use the SevenBit option! So I try again, this time using the following code:

'create an alternate view of the RTF data from our RichTextBox Control Dim rtfView As Mail.AlternateView = Mail.AlternateView.CreateAlternateViewFromString(Me.RichTextBox1.Rtf) rtfView.ContentType.MediaType = Mime.MediaTypeNames.Text.RichText 'format our new view as rich text... rtfView.TransferEncoding = Mime.TransferEncoding.SevenBit 'and send unencoded as 7-bit ASCII 'send our email as plain text and with a rich text alternater view SendEMail("[email protected]", "[email protected]", "Test", Me.RichTextBox1.Text, False, "smtp.80micro.com", rtfView)

The results were spectacular. You may also want to apply SevenBit encoding to HTML and Plain Text

data, as long as they do not contain any embedded 8-bit coding. Consider my final result:

From: [email protected] To: [email protected] Date: 25 Feb 2011 14:22:46 -0500 Subject: Test Content-Type: multipart/alternative; boundary=--boundary_0_f3de9980-d92b-423b-84a7-cd77f3a35def ----boundary_0_f3de9980-d92b-423b-84a7-cd77f3a35def Content-Type: text/plain; charset=us-ascii Content-Transfer-Encoding: quoted-printable This is a test of Rich Text data.=0A Note the vbLf terminator here in the plain text ----boundary_0_f3de9980-d92b-423b-84a7-cd77f3a35def Content-Type: text/richtext; charset=utf-8 Note text/richtext Content-Transfer-Encoding: 7bit Note 7bit encoding (or rather, 7bit indicating NO ENCODING) \rtf1\ansi\ansicpg1252\deff0\deflang1033\fonttbl\f0\fnil\fcharset0 Times New Roman;\f1\fnil\fcharset0 Microsoft Sans Serif; \colortbl ;\red0\green0\blue0; \viewkind4\uc1\pard\cf1\f0\fs24 This is a \b test \b0 of \i Rich Text data\i0 .\par \cf0\f1\fs17\par Blank line is due to an additional embedded vbCrLf code in the Rich Text data ----boundary_0_f3de9980-d92b-423b-84a7-cd77f3a35def—

Were we to plug the above raw Rich Text data without modification into the Rtf

property of a RichTextBox, we would see the result shown on the right.

Typical Email Server Specifications: Comcast SMTP/POP3 Info: POP server: mail.comcast.net (Port 995) POP SSL required? Yes (If No, use POP3 Port 110 and pop3.comcast.net server) User name: Your email address, for example [email protected] Password: The password you usually use to sign in to Comcast SMTP server: smtp.comcast.net (Port 587) Authentication required? Yes (this matches your POP username and password) SMTP TLS/SSL required? Yes (If No, use SMTP Port 25)

GMAIL SMTP/POP3 Info: POP server: pop.gmail.com (Port 995) POP SSL required? Yes User name: Your Gmail address, for example [email protected] Password: The password you usually use to sign in to Google and Gmail SMTP server: smtp.gmail.com (Port 587) Authentication required? Yes (this matches your POP username and password) SMTP TLS/SSL required? Yes

HOTMAIL SMTP/POP3 Info: POP server: pop3.live.com (Port 995) POP SSL required? Yes User name: Your Hotmail address, for example [email protected] Password: The password you usually use to sign in to Hotmail SMTP server: smtp.live.com (Port 587) Authentication required? Yes (this matches your POP username and password) SMTP TLS/SSL required? Yes

Yahoo! SMTP/POP3 Info (paid service): POP server: pop.mail.yahoo.com (Port 995) POP SSL/TLS required? Yes User name: Your Yahoo email address, for example [email protected] Password: The password you usually use to sign in to Yahoo SMTP server: smtp.mail.yahoo.com (Port 465 for SSL, or 587 for TLS) Authentication required? Yes (this matches your POP username and password) SMTP TLS/SSL required? Yes

COX SMTP/POP3 Info: POP server: pop.cox.net (Port 995) POP SSL/TLS required? Yes User name: Your Cox email address, for example [email protected] Password: The password you usually use to sign in to Cox SMTP server: smtp.juno.net (Port 465 for SSL, or 587 for TLS) Authentication required? Yes (this matches your POP username and password) SMTP TLS/SSL required? Yes NOTE: Some Cox servers use pop.west.cox.net, plus “east” and “central”. Refer to the

link in the note, below.

Juno SMTP/POP3 Info: POP server: pop.juno.com (Port 995) POP SSL required? Yes User name: Your Juno email address, for example [email protected] Password: The password you usually use to sign in to Juno SMTP server: authsmtp.juno.com (Port 465) Authentication required? Yes (this matches your POP username and password) SMTP TLS/SSL required? Yes

Most other servers SMTP/POP3 Info (Verizon, NetZero, etc.): POP server: pop.xxx.com (Port 110 (default)) POP SSL required? No User name: Your email address, for example [email protected] Password: The password you usually use to sign in to your server SMTP server: smtp.xxx.com (Port 25 (default)) Authentication required? Yes (this matches your POP username and password) SMTP TLS/SSL required? No

NOTE: The above settings will also work with Comcast. The above is the typical

settings for “Plain” (unsecured) email. SSL/TLS email, on the other hand is encrypted for security. NOTE: Look to www.defcon-5.com/support/index.cfm?docid=95 for others. Such as

ATT, East, Central, and West COX servers, AOL, EarthLink, etc.

NOTE: Always check with your provider. Use a web search for something like “pop3 xxx settings”, though using your provider in place of xxx.

NOTE: If you change your email settings, you should reboot your computer to ensure the new settings will work with the .NET Mail interface.

NOTE: TLS is usually an option available for smartphones, mobile PCs, etc., though using TLS ports with SSL usually works

Page 18: Send (SMTP) and Retrieve (POP3) Email with Ease under VB.NET

–18–

PART TWO

Encoding and Decoding Email Data

When we send and receive email, the parameters noted for the Content-Type and Content-Transfer-Encoding fields are of great importance. If you provide the wrong information, the receiving email

reader can get confused. When there is confusion at either end, they default to assuming a Content Type

of “application/octet-stream” (a binary stream) and “Base64” for Content-Transfer-Encoding. The goal

is never to lose original content. For example, if you specify a Quoted-Printable-class encoder, but the

data is 8-bit, then the transmitter will supersede your option and specify Base64 encoding.

But where does all this come from? Where and how can we tell VB.NET how to transmit these

instructions? What if I can barely understand “application/octet-stream,” let along spell it consistently?

And what in blue blazes is Base64 encoding, anyway?

NOTE: Blue Blazes refers to very hot Hell fire, coming from the extremely hot blue flame given off by the likes of propane.

The .NET MIME processor has two built-in classes that will help you easily resolve the Content-Type

and Content-Transfer-Encoding issues. The first helper is the System.Net.Mime.TrasferEncoding

enumeration, and the second helper is the System.Net.Mime.ContentType class.

Based on the kind of conversion we specify for encoding, the Content-Transfer-Encoding mechanism

will reflect one of three values (a fourth, Unknown, is presently defined, but is never used):

Member: Description:

QuotedPrintable (0) Encodes data that consists of printable characters in the US-ASCII character set. See RFC 2045 Section 6.7. All Text encoders default to this (US-ASCII, UTF7, UTF8, etc.). Maximum line length is 75 characters. Stored in email as “quoted-printable”.

Base64 (1) Encodes stream-based data. See RFC 2045 Section 6.8. Best for Binary, or any code you do not want to deal with control-code conversion Hex-Tags. Maximum line length is 75 characters. Stored in email as “base64”.

SevenBit (2) Used for data that is not encoded. The data is in 7-bit US-ASCII characters with a total line length of no longer than 1000 characters. See RFC 2045

Section 2.7. Stored in email as “7bit”.

Unknown (-1) This is currently not supported and will never be selected by the SMTP server.

NOTE: RFC 2045 can be found at www.ietf.org/rfc/rfc2045.txt. Also, the MSDN documentation actually erroneously references RFC 2406, which in fact documents the IP Encapsulating Security Payload (ESP). RFC 2045 refers correctly to Multipurpose Internet Mail Extensions, (MIME) Part One: Format of Internet Message Bodies.

Therefore, if we are passing ASCII data, such as Plain Text, HTML, or Rich Text, it will be encoded as

QuotedPrintable as long as we specify a text-based method from Encoding. If we leave this field blank,

or the data is not 7-bit text, it can be expected to be processed as Base64. And that is why we tend to get

Base64 as the Content-Transfer-Encoding type a lot on Attachments and Alternate Views.

For specifying the data for the Content-Type field, we look to System.Net.Mime.MediaTypeNames.

MediaTypeNames specifies 3 types of interest, which are most notably useful for regular attachments:

Member: Description: Application Specifies the kind of application data in an email message attachment or AlternateView

Image Specifies the type of image data in an email message attachment or AlternateView

Text Specifies the type of text data in an email message attachment or AlternateView

The MediaTypeNames.Application type exposes the following default members:

Member: Context-Type Field Reports: Description: Octet application/octet-stream Specifies that the Application data is not interpreted (an Octet-stream (Byte-stream)).

Pdf application/pdf Specifies that the Application data is in Portable Document Format (PDF).

Rtf application/rtf Specifies that the Application data is in Rich Text Format (RTF).

Soap application/soap+xml Specifies that the Application data is a SOAP document (Simple Object Access Protocol).

Zip application/zip Specifies that the Application data is compressed.

If you want to specify other sub-types, you can, but there is no guarantee that someone else’s email

processor will support it. The .NET MediaTypeNames class strictly follows the RFC 2045 specification.

The MediaTypeNames.Image type exposes the following default members:

Member: Context-Type Field Reports: Description:

Gif image/gif Specifies that the Image data is in Graphics Interchange Format (GIF).

Jpeg image/jpeg Specifies that the Image data is in Joint Photographic Experts Group (JPEG) format.

Tiff image/tiff Specifies that the Image data is in Tagged Image File Format (TIFF).

Page 19: Send (SMTP) and Retrieve (POP3) Email with Ease under VB.NET

–19–

The MediaTypeNames.Text type exposes the following members:

Member: Context-Type Field Reports: Description:

Html text/html Specifies that the Text data is in HTML format.

Plain text/plain Specifies that the Text data is in Plain Text format.

RichText text/richtext Specifies that the Text data is in Rich Text Format (RTF).

Xml text/xml Specifies that the Text data is in XML format.

Allowing Users to Specify Content-Type and Content-Transfer-Encoding Options Of course, the options you offer your email application users can differ, or can even be similar to the

previously mentioned tables. However, you will need to have a mechanism in place for either

interpreting their Attachment/Alternate View choices, or for allowing them to explicitly specify them.

The easier you make it for them, the more trust and loyalty they will have for your products. Just do not

make it so brain-dead-simple that you begin to restrict them in their options.

If you refer to the ReadEmail() method comments, you will notice that the specification for an

Alternate View showed you how to stipulate a different Content-Type and Content-Transfer-Encoding

type by using VB programming methods. Well, this all well and good when you are programming such

code, but it is not very convenient for your users if they wish to specify such things, nor is adding

parameterized declarations for an attachment. For example, consider having to hand-type “C:\My

Files\API32.txt (text/plain, SevenBit)”, and even more so for a binary file, like “C:\telnet.exe

(application/octet-stream, Base64)”.

It would of course be more convenient to allow them to select the files from a browser and for you to

either interpret their attachment and determine how best to transmit them (this is not as difficult as you

might think), or allow them, maybe as an advanced user option, to specify both parts themselves from

dropdown ComboBox controls, and then you would simply wrap them up in a manner recognizable by

the SendEmail() method, though I would imagine that you would customize the code to your needs, such

as adding trendy features like specifying email recipients by name, rather than just their full email

addresses. This makes selecting recipients more convenient, such as using a current naming format that

is popular and is already supported by the .NET Mail object, like the following: "David Ross Goben"

<[email protected]>.

Consider the following structure and code to simplify defining Content-Type text:

'******************************************************************************* ' Enum MediaTypes: Enumeration used by GetMediaType '******************************************************************************* Public Enum MediaTypes As Integer ApplicationOctet ' 0 = Integer Value ApplicationPdf ' 1 ApplicationRtf ' 2 ApplicationSoap ' 3 ApplicationZip ' 4 ImageGif ' 5 ImageJpeg ' 6 ImageTiff ' 7 TextHtml ' 8 TextPlain ' 9 TextRich '10 TextXml '11 End Enum '******************************************************************************* ' Function Name : GetMediaType ' Purpose : Provide easy access to System.Net.Mime.MediaTypes text ' : ' Returns : provided a MediaTypes enumeration value, a string representing ' : the selected type will be returned. '******************************************************************************* Public Function GetMediaType(ByVal MediaType As MediaTypes) As String Select Case MediaType Case MediaTypes.ApplicationPdf Return "application/pdf" Case MediaTypes.ApplicationRtf Return "application/rtf" Case MediaTypes.ApplicationSoap Return "application/soap+xml" Case MediaTypes.ApplicationZip Return "application/zip" Case MediaTypes.ImageGif

Page 20: Send (SMTP) and Retrieve (POP3) Email with Ease under VB.NET

–20–

Return "image/gif" Case MediaTypes.ImageJpeg Return "image/jpeg" Case MediaTypes.ImageTiff Return "image/tiff" Case MediaTypes.TextHtml Return "text/html" Case MediaTypes.TextPlain Return "text/plain" Case MediaTypes.TextRich Return "text/richtext" Case MediaTypes.TextXml Return "text/xml" Case Else Return "application/octet-stream" End Select End Function

For options to the user, you can present those options to them however you wish (or they wish), such as

the text displayed within the enumeration in a ListBox or ComboBox control, presented to them in

prettier or simpler text, such as “Application/Octet” or even “Binary”, or even by first separating the

three types, Application, Image, and Text, and then present secondary subtype lists for each. The

methods for how to do this are many. Simply ensure that the final values match those listed in the

comments for their integer values. You can then supply this integer value (or first cast it, using

something like “DirectCast(Value, MediaTypes)”) to the GetMediaType() function, which will return the

appropriate, email-friendly Content-Type text.

For simplifying Content-Transfer-Encoding, consider the following simple structure and method:

'******************************************************************************* ' Enum TransferEncodings: Enumeration used by GetTransferEncoding '******************************************************************************* Public Enum TransferEncodings As Integer QuotedPrintable ' 0 = Integer value Base64 ' 1 SevenBit ' 2 End Enum '******************************************************************************* ' Function Name : GetTransferEncoding ' Purpose : Provide easy access to System.Net.Mime.TransferEncoding data ' : ' Returns : Provided a TransferEncodings value, a TransferEncoding value ' : is returned. '******************************************************************************* Public Function GetTransferEncoding(ByVal TransferEncoding As TransferEncodings) As System.Net.Mime.TransferEncoding Return DirectCast(TransferEncoding, System.Net.Mime.TransferEncoding) End Function

Using techniques similar to those already outlined for GetMediaType(), you can do the same thing for your

users, allowing them to specify how they want their data to be encoded.

Ultimately, you are the one who must specifically inform the SMTP server of the type and encoding.

Since an Alternate View object’s MediaType parameter (MyAlternateView.ContentType.MediaType)

expects a string, setting it is easy – you just supply it with the string you got from the GetMediaType()

function. Also, because the Alternate View’s Transfer Encoding (MyAlternateView.TrasferEncoding)

expects an integer cast to System.Net.Mime.TrasferEncoding, you can simply provide it the value

returned by the GetTransferEncoding() method, or, simply use the (no real code) DirectCast() method

to cast their ComboBox selection index (0-2) to System.Net.Mime.TrasnferEncoding.

Page 21: Send (SMTP) and Retrieve (POP3) Email with Ease under VB.NET

–21–

Determining if Text can be Encoded As Quoted-Printable, Base64, or 7Bit When you are preparing to specify coding, or the lack thereof for text, you need to ensure that if you are

going to submit it as using as Quoted-Printable or 7Bit, that it will not be forced to Base64, if it ends up

containing any 8-bit data, unless that is what you want to happen. If not, you can first scan it with the

following little function, which will quickly determine if the code contains 8-bit data:

'******************************************************************************* ' Function Name : TextNeedsEncoding ' Purpose : Determine if HTML text, Rich Text, or Plain Text requires ' : 8-bit code translation to 7-bit Quoted-Printable tags. ' : ' Returns : Provided a source string, it returns a boolena flag. ' : If the returned value is true, the source contains 8-bit data ' : and will be encoded by server. ' : ' NOTES : If text data contains 8-bit values, the default .NET ' : SMTP processor will force this code to be encoded to Base64, ' : even if only a single byte is 8-bit. ' : ' : To avoid this, the Force7BitHtml() method can be invoked on ' : HTML text to ensure that it is 7-bit encoded so that it can ' : be processed as Quoted-Printable or as 7Bit. The ForceQuotedPrintable() ' : method performs essential conversions for non-HTML text, but this ' : would be best served in Attachments and Alternate Views. '******************************************************************************* Public Function TextNeedsEncoding(ByVal Message As String) As Boolean Dim Byt() As Byte = Encoding.UTF8.GetBytes(Message) 'convert message to byte array For Each B As Byte In Byt If CBool(B And &H80) Then Return True Next Return False End Function

If the TextNeedsEncoding() method returns True and you are processing HTML text, you can pass the

HTML text through the Force7BitHtml() function (listed below), which will ensure, upon return, that all

codes in the HTML text is 7-bit, and that any 8-bit or 16-bit code that was in it is dutifully converted to

7-bit HTML Entity Numbers. If you are passing the function Rich Text or Plaint Text, you can

specifically Hex-Tag its 8-bit characters (something that the default Quoted-Printable encoders will not

do, even though it is permitted) using the ForceQuotedPrintable() function (listed further below).

Converting 8-Bit HTML Data to 7-Bit without Loss of Integrity Consider the following simple HTML upgrade function that will transparently convert any HTML text that contains 8-bit or 16-bit data to fully compatible 7-bit HTML text: '******************************************************************************* ' Function Name : Force7BitHtml ' Purpose : Method to convert 8-bit code in an HTML message to 7-bit. ' : ' Returns : Provided a string containing HTML code, it will return a string ' : containing HTML code that does not have any 8-bit data embedded. ' : ' NOTES : If any characters in an HTML text string are 8-bit (values ' : greater than 127), then they are converted into a special ' : 7-bit HTML Entity Number, For Example, code 149 (•) is an 8-bit ' : value that can be changed to HTML "&#149;", which will ensure ' : that it will still be displayed on the HTML page, but the HTML ' : souce code will no longer carry an actual 8-bit value. If such ' : code had not been corrected, the encoding of the data would be ' : forced to change from quoted-printable to Base64, because that ' : would be the only way the email processor could guarantee that ' : the email text was fully intact. '******************************************************************************* Public Function Force7BitHtml(ByVal HtmlSource As String) As String Dim Sb As New StringBuilder 'set up string builder for appending data For Idx As Integer = 1 To Len(HtmlSource) Dim C As Integer = AscW(Mid(HtmlSource, Idx, 1)) 'get a single character from the source Select Case C 'check each character Case Is > &H7F, Is < 0 'if 8-bit or unicode code Sb.Append("&#" & C.ToString & ";") 'convert to 7-bit HTML ecoder Case Else Sb.Append(ChrW(C)) 'else save text regardless End Select Next Return Sb.ToString End Function

Page 22: Send (SMTP) and Retrieve (POP3) Email with Ease under VB.NET

–22–

Converting 8-Bit Text Data to 7-Bit without Data Loss If you test a text string with the TextNeedsEncoding() function and it comes back True, you can either

encode it using Base64, or, if you want it to remain readable in raw format, you can convert it to a Hex-Tagged 7-bit text format by passing it through the ForceQuotedPrintable() function, listed here:

'******************************************************************************* ' Function Name : ForceQuotedPrintable ' Purpose : Force 8-bit code in a text message to 7-bit, without data loss. ' : ' Returns : Provided a source string that contains 8-bit data, the 8-bit ' : data is converted to Hex-Tags, and the returned string is 7-bit. ' : ' NOTES : if any characters in a text string are 8-bit (values greater ' : than 127), then they are converted into special 7-bit tags. ' : For Example, code 149 (•) is an 8-bit value that can be changed ' : to hex "=95", which will ensure that it will still be displayed ' : in the text, but the text data will no longer carry an actual ' : of the data would be forced to change from quoted-printable or 7bit ' : to Base64, because that would be the only way the email processor ' : Base64, because that would be the only way the email processor ' : could guarantee that the email text was fully intact. However, ' : you will have to use the DecodeQuotedPrintable() method to convert ' : it back to its original text form. ' : ' : The Encoded text will begin with "=00". Because unencoded null codes ' : are not permitted in email data, you can use this to instantly ' : determine on the receiving end that this code will need to be ' : processed by DecodeQuotedPrintable() a second time (if initially ' : encoded as Quoted-Printable). A second pass would be required, ' : because if this translated code was afterward encoded as Quoted- ' : Printable, and all the "=xx" byte-translations, would be ' : reinterpreted as "=3Dxx", which DecodeQuotedPrintable() would ' : convert back to "=xx", so passing through a second time would ' : properly convert the additional encoding. Further, by checking the ' : text startiing with "=00", you would know that you would need to ' : double-decode the text. Also, you would want to initially skip this ' : initial tag when passing it the second time to DecodeQuotedPrintable(): ' : ' : Dim Result As String = DecodeQuotedPrintable(Message) 'initially decode Quoted-Printable text ' : If VB.Left(Result, 3) = "=00" Then 'tagged as pre-encoded? ' : Return DecodeQuotedPrintable(Mid(Result, 4)) 'yes, so decode again and return, less initial null byte ' : Else ' : Return Result 'otherwise, return result of decoding ' : End If '******************************************************************************* Public Function ForceQuotedPrintable(ByVal Message As String) As String Dim Byt() As Byte = Encoding.UTF8.GetBytes(Message) 'convert message to byte array Dim Sb As New StringBuilder("=00") 'set up string builder for appending data For Each B As Byte In Byt Select Case B 'check each byte Case Is > &H7F 'if 8-bit code Sb.Append("=" & Hex(B)) 'convert to 7-bit tag Case Else Sb.Append(Chr(B)) 'else save text regardless End Select Next Return Sb.ToString End Function

The ForceQuotedPrintable() method uses the same encoding tags for 8-bit code that are used by

Quoted-Printable encoding for control codes. However, if you also pass it as Quoted-Printable, this

means that you will have to decode it twice (discussed next). The first time to decode the “=” tags added

by the internal SMTP processor, and a second time to decode the tags that you added using the above

method prior to SMTP encoding the data as Quoted-Printable.

You may notice that the ForceQuotedPrintable() method leads the result text with “=00”. You can

simply check for this in your email processor, and if found, as shown in the above comments, you can

decode it a second time. This can be done because the above encoder may add Hex-Tags for 8-bit

values, but the internal SMTP processor will not know that these are Hex-Tags, because it would not

expect them, so it would encode “=95” to “=3D95”, where the “=3D” is the equals sign encoded (this

symbol has a hex ASCII value of &H3D). This will decode to “=95”, and then the “=95” will in turn

have to be decoded in a second pass (&H95 is 149 decimal, and represents the “•” symbol).

Of course, you can also ‘encode’ this data in an attachment or alternate text as 7Bit, but you should still

check for the leading “=00” tag, to see if you will still need to decode it back to 8-bit text.

NOTE: Be reminded that other email processors will not have this kind of arcane knowledge, and so their users will still see any Hex-Tag encoding that the ForceQuotedPrintable() method had placed in there.

Page 23: Send (SMTP) and Retrieve (POP3) Email with Ease under VB.NET

–23–

Decoding Quoted-Printable Text Quoted-Printable text has a line length limit set at about 75 characters (including the line terminator). I

see it more as a “when you find out that you are at 72-75 characters, then do a forced soft line wrap”.

All text encoded using the Quoted-Printable method will have Hex-Tag encoding for any control codes,

except maybe extremely simply 1-line notes. Regardless, run all Quoted-Printable encoded data through

the following DecodeQuotedPrintable() method to render them into their original format.

'******************************************************************************* ' Function Name : DecodeQuotedPrintable ' Purpose : Method to clean typical control translations, or all of them. ' : This should be invoked for all data coded Quoted-Printable. ' : ' Returns : Provided a raw message string block, it returns a decoded string. ' : ' NOTES : Typical cleaning involves changing "=0D" to vbCr, "=0A" to vbLf, ' : "=20" to a space, and "=3D" to "=", plus any line wrap ' : terminators at the end of lines to vbNullstring. ' : ' : A StringBuilder object will be used, which will very quickly ' : do a replacement of all control code translations using fewer ' : resources, and what resources that are used will be instantly ' : flushed when the method exits. '******************************************************************************* Public Function DecodeQuotedPrintable(ByVal Message As String, Optional ByVal QuickClean As Boolean = False) As String 'set up StringBuilder object with data stripped of any line continuation tags Dim Msg As New StringBuilder(Message.Replace("=" & vbCrLf, vbNullString)) If QuickClean Then 'perform a quick clean (clean up common basics) Return Msg.Replace("=" & vbCrLf, vbNullString).Replace("=0D", vbCr).Replace("=0A", _ vbLf).Replace("=20", " ").Replace("=3D", "=").ToString Else 'perform total cleaning 'store 2-character hex values that require a leading "0" Dim HxData As String = "X0102030405060708090A0B0C0D0E0F" For Idx As Integer = 1 To &HF 'initially process codes 1-15, which require a leading zero Msg.Replace("=" & Mid(HxData, Idx << 1, 2), Chr(Idx)) 'replace hex data with single character code (SHIFT is faster) Next For idx As Integer = &H10 To &HFF 'process the whole 8-bit extended ASCII gambit Msg.Replace("=" & Hex(idx), Chr(idx)) 'replace hex data with single character code Next Return Msg.ToString 'return result string End If End Function

The above function will execute much faster than the conversion method demonstrated a little earlier,

plus it can perform an optional “quick clean” for minor jobs, or a detailed cleaning in just about an

instant (but I wish a StringBuilder would do blind replacements (MID()-like operations) so I could make

it even faster, plus a Find method to emulate Instr()-type functionality would also be nice).

Basically, what this method does is convert the Hex-Tags with their 2-character hexadecimal digits (0-9,

A-F) back to their original 8-bit character codes (> &H7F), or their original control codes (< &H20). A

special case was spaces “=20”, which, if at the end of a line, we often lost, so they were encoded. On top

of that, because there was an imposed 75-character line limit, soft line-wraps were added by place a ‘=’

at the end of a forced returned line. The DecodeQuotedPrintable() method reverses that, providing you

with pristine text data that was formatted exactly as it had been provided to the server.

Translating Base64 Data Back to Its Original Format For all the trouble I had earlier with Base64, converting it to Text or a binary format is not difficult at all.

Base64, for those of you who have been waiting with bated breath for a definition, is really simple

enough, and is laid out much like a Lucky Charms™ secret decoder ring, but with some Umph! (And

why does their breath smell like fish bait? Or was that restrained, or anticipating breath?)

Base64 represents binary (or 8-bit) data in an ASCII string by translating the data into a radix-64 format.

Various forms of this are commonly used in applications when there is a need to encode binary data that

needs be stored and transferred over media that are designed to deal with strictly 7-bit text data. This is

to guarantee that the data remains fully intact without modification during transport, and can be decoded

to 100% of its orginal form. This type of encoding was needed to initially support dialup communication

between systems running different operating systems. For example, Base64-type ecoding for an

uppercase-only, symbol-rich character set was implemented as uuencode by UNIX, and BinHex by the

Page 24: Send (SMTP) and Retrieve (POP3) Email with Ease under VB.NET

–24–

TRS-80 (this format was later adapted for the Macintosh). Because of their compatability, a messaging

application could make better assumptions about what characters were safe to use.

Base64, as used by MIME, has a 64-character set defined

in the table shown to the right, where a plain ASCII

character is used to represent a numeric index:

Each byte of text is composed of 8 bits. Were we to look at

any bytes in radix-2 (Base2, or Binary); it would look like

a string of 8 ones and/or zeros.

For example, each letter of my name, David, is represented by the decimal ASCII codes 68, 97, 118, 105, and 100. In

hexadecimal, which can be computed using the Hex() function, it would be &H44, &H61, &H76, &H69, and

&H64. These are represented in binary streams 01000100, 01100001, 01110110, 01101001, and 01100100. Together,

we get 0100010001100001011101100110100101100100.

Rather confusing, I know (but strangely, it is actually quite

easy to translate in your head, once you know hexadecimal).

NOTE: Each letter of a hex value – 0 to F (15 decimal) – represents 0000, 0001, 0010, 0011, 0100, 0101, 0110, 0111, 1000, 1001, 1010, 1011, 1100, 1101, 1110, and 1111 in Binary, where the position values from left to right are 8, 4, 2, and 1.

If we look at this stream as a table that is sectioned off at 8 bits, it is less confusing, and less at 4 binary

bits. But we are more interested in breaking this up into Base64, which uses only 6 bits (values 0-63, or

00-3F hex, or 00|0000 to 11|1111 binary). So examine this long stream of bits in that light:

You can compare the bit patters to the upper, 8-bit blocks for Base256, and the lower, 6-bit blocks for

Base64. The Hex index takes 6 consecutive bits and converts them to Base16, or hexadecimal. That is, the

first 2 bits comprise the first hex value (0-3), and the remaining 4 bits comprise the second hex value (0-F).

So if we take the first 6 bits of the 8-bit value for the letter “D” (0100|0100), which is 01|0001, we get 11

Hex, or 1 x 16 + 1, or 17. The value 17 corresponds to the ASCII letter “R” in the previous Base64 table.

Explaining all this can make it seem complicated, but to actually do it is amazingly easy.

You may notice a couple of other things. First, we padded the 8-bit data out 2 extra bits, so that we could

accommodate the 6-bits required for the last Base64 value. Second, you may notice that 3 8-bit values fit

nicely into 4 6-bit values (24-bits total). Because of this, what Base64 conversion does is process the

original 8-bit values in groups of 3 bytes at a time, which yields 4 Base64 values for each 3-byte group.

However, it is clear that the original data cannot, and should not be always divisible by 3. So conversion

routine uses padding to fill things out. It pads the data by adding 1 to 3 ‘=’ symbols at the end of the

encoding run (this is why you will frequently see them trailing Base64-encoded data).

What these pad characters are used for is to pad out missing codes from the final 4-character Base64

grouping. They are treated as null values and tossed away, but the decoder, when grabbing values, will

first check if it is the end of the data, and if not, it will simply slog in the next set of 4 characters from

Char Index Char Index Char Index Char Index

A 00 Q 16 g 32 w 48

B 01 R 17 h 33 x 49

C 02 S 18 i 34 y 50

D 03 T 19 j 35 z 51

E 04 U 20 k 36 0 52

F 05 V 21 l 37 1 53

G 06 W 22 m 38 2 54

H 07 X 23 n 39 3 55

I 08 Y 24 o 40 4 56

J 09 Z 25 p 41 5 57

K 10 a 26 q 42 6 58

L 11 b 27 r 43 7 59

M 12 c 28 s 44 8 60

N 13 d 29 t 45 9 61

O 14 e 30 u 46 + 62

P 15

f 31

v 47

/ 63

Page 25: Send (SMTP) and Retrieve (POP3) Email with Ease under VB.NET

–25–

the feeder stream. So throwing in some padding is like throwing some old clodhoppers into the bottom

of the last bushel of pears – it makes it look as full as the others (I actually did this on my grandparent’s

farm, and the accentuated and prolonged stinging on my hindquarters informed me that this was an

inappropriate course of action, warranting further personal review within the acetic seclusion of the TV-

less, toy-less, book-less confines of Grandma’s sewing room, unless I wanted to flip on the radio and

catch up on pork-belly futures – but the good news was, I did figure out how to sew).

NOTE: Clodhoppers are big, heavy shoes farmers use to protect their feet and ankles while walking through tilled fields.

Suppose we took the Base64-encoded data that was in one of my earlier, and at the time presumed

failed, AlternateView attempts for my Rich Text data:

From: [email protected] To: [email protected] Date: 22 Feb 2011 17:19:08 -0500 Subject: Test Content-Type: multipart/alternative; boundary=--boundary_0_253a0455-dce6-4622-b262-ae5f1cfc0321 ----boundary_0_253a0455-dce6-4622-b262-ae5f1cfc0321 Content-Type: text/plain; charset=us-ascii Content-Transfer-Encoding: quoted-printable This is a test of Rich Text data.=0A ----boundary_0_253a0455-dce6-4622-b262-ae5f1cfc0321 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: base64 This blank line after the Content-Transfer-Encoding line is required, and is not part of the message e1xydGYxXGFuc2lcYW5zaWNwZzEyNTJcZGVmZjBcZGVmbGFuZzEwMzN7XGZvbnR0Ymx7XGYw XGZuaWxcZmNoYXJzZXQwIFRpbWVzIE5ldyBSb21hbjt9e1xmMVxmbmlsXGZjaGFyc2V0MCBN aWNyb3NvZnQgU2FucyBTZXJpZjt9fQ0Ke1xjb2xvcnRibCA7XHJlZDBcZ3JlZW4wXGJsdWUw O30NClx2aWV3a2luZDRcdWMxXHBhcmRcY2YxXGYwXGZzMjQgVGhpcyBpcyBhIFxiIHRlc3Qg XGIwIG9mIFxpIFJpY2ggVGV4dCBkYXRhXGkwIC5ccGFyDQpccGFyZFxjZjBcZjFcZnMxN1xw YXINCn0NCg== ----boundary_0_253a0455-dce6-4622-b262-ae5f1cfc0321--

If this data (marked in blue in the above block) was stored in a string variable named b64Data, then we

could stuff it into our RichTextBox1 control using the following single line of code:

Me.RichTextBox1.Rtf = Encoding.UTF8.GetChars(System.Convert.FromBase64String(b64Data.Replace(vbCrLf, vbNullString)))

And that is it! But I think you may want to also look at what I did a little less cryptically:

b64Data = b64Data.Replace(vbCrLf, vbNullString) 'remove artificial email line terminators Dim Byt() As Byte = System.Convert.FromBase64String(b64Data) 'convert from Base64 to a byte array of ASCII values Me.RichTextBox1.Rtf = Encoding.UTF8.GetChars(Byt) 'convert 8-bit ASCII values in Byte array to a String Array

But what about translating binary data? Actually, the first two lines of code will properly decrypt binary

code from Base64 format to a binary array of bytes, which means we are already half way there!

If we want to save this array of bytes to a file, and it was a JPEG file, which the Content-Type file may

have informed us of by stating “Content-Type: image/jpeg; name=NewImage.JPG”, we could do this:

b64Data = b64Data.Replace(vbCrLf, vbNullString) 'remove artificial email line terminators Dim Byt() As Byte = System.Convert.FromBase64String(b64Data) 'convert from Base64 to a binary byte array Dim sw As New System.IO.FileStream("C:\MiscIO\NewImage.JPG", IO.FileMode.OpenOrCreate) 'create destination sw.Write(Byt, 0, UBound(Byt) + 1) 'write file from Byte array. Stream all bytes sw.Close() 'close file (automatically invoke Dispose)

But suppose we want to display it within a PictureBox control without first saving it to a file? What

would we do then? The easy and fast way is to instead simply take advantage of a MemoryStream

object, which is very much like a file stream, except that the stream remains entirely in memory:

b64Data = b64Data.Replace(vbCrLf, vbNullString) 'remove artificial email line terminators Dim Byt() As Byte = System.Convert.FromBase64String(b64Data) 'convert from Base64 to a byte array of ASCII values Dim ImgStrm As New System.IO.MemoryStream(Byt) 'convert byte array to a memory stream Me.PictureBox1.Image = Image.FromStream(ImgStrm) 'import the memory stream to an image object ImgStrm.Close() 'close the memory stream (automatically invoke Dispose)

Or, how about this one-liner that will do exactly the same thing:

Me.PictureBox1.Image = Image.FromStream(New System.IO.MemoryStream(System.Convert.FromBase64String(b64Data.Replace(vbCrLf, vbNullString))))

NOTE: Do not worry about closing the memory stream, as we did in the previous example. It will automatically close when it falls out of scope, which happens after the picture box received the image. This is the real beauty with VB.NET being a fully-functional object oriented programming language, and finally having real, and thankfully strict scoping rules.

Page 26: Send (SMTP) and Retrieve (POP3) Email with Ease under VB.NET

–26–

Consider the following two functions, which will decode raw Base64-encoded message string data: '******************************************************************************* ' Function Name : DecodeBase64ToStr ' Purpose : Decode a provided raw email message string that is encoded to Base64. ' : ' Returns : Decoded String ' : ' NOTES : note that the lone vbCrLf at the end of lines is filtered out. '******************************************************************************* Public Function DecodeBase64ToStr(ByVal strData As String) As String Return Encoding.UTF8.GetChars(DecodeBase64ToBytes(strData)) End Function '******************************************************************************* ' Function Name : DecodeBase64ToBytes ' Purpose : Decode a provided raw email message string that is encoded to Base64. ' : ' Returns : Decoded binary Byte Array ' : ' NOTES : note that the lone vbCrLf at the end of lines is filtered out. '******************************************************************************* 'this modification returns a Byte Array of the Base64 encoded source data Public Function DecodeBase64ToBytes(ByVal strData As String) As Byte() Return System.Convert.FromBase64String(strData.Replace(vbCrLf, vbNullString)) End Function

The first function, DecodeBase64ToStr() is meant to process text data that has been encoded to Base64,

but is know to have a text result (based upon the result of the Content-Type field, for example). The

second method, DecodeBase64ToBytes(), is meant to process a binary data result, returning a Byte array.

You can afterward process the byte array as you see fit, such as saving it to a file, or rendering it if it is a

renderable object, such as an image, using the Memory Stream technique shown, above.

Notice, finally that the DecodeBase64ToStr() method takes full advantage of the DecodeBase64ToBytes()

method, because the data must always be first rendered to a Byte array.

Translating BinHex Data Back to its Original Format BinHex was first written by Tim Mann for the TRS-80. It is actually an inefficinet format, converting

every byte into two, but it was essential back in the mid-1980s to tranfer binary data over the internet by

services like CompuServe, which for some time did not support 8-bit data transfer. BinHex was a fix that

just stuck, and would have come along even if Tim had not written it. Even the MacIntosh adopted it.

NOTE: Anyone who knocks the TRS-80 computer, calling it by such derogatory terms as “Trash-80”, is speaking from a position of total ignorance. It was an essential technological platform that enabled you and me to be sitting here today with our PCs and our iMacs, our smart phones, tablet PCs, laptops, PDAs, and most other gadgets. Had the TRS-80 failed and had not become a leader in the PC market, you would be looking at a much different and more primitive world right now.

For those who discover that they have such data, they can decode it quite easily. Just supply the

DecodeBinHex() function with a raw hex string, such as the data provided in an email, and it will return a

Byte array with the converted data. Considering the following method:

'******************************************************************************* ' Function Name : DecodeBinHex ' Purpose : Decode a provided raw email message string that is encoded to BinHex. ' : ' Returns : Decoded String ' : ' NOTES : note that the lone vbCrLf at the end of lines is filtered out. '******************************************************************************* Public Function DecodeBinHex(ByVal StrData As String) As Byte() Dim Src() As Byte = Encoding.UTF8.GetBytes(StrData.Replace(vbCrLf, vbNullString).ToUpper) Dim Result() As Byte 'init output buffer ReDim Result(UBound(Src) \ 2) 'set initial dimension to 1024 bytes (includes offset 0) Dim Index As Integer = 0 'init index for Result() array For Idx As Integer = 0 To UBound(Src) Step 2 'scan the string, 2 hex characters at a time Dim CL As Integer = Src(Idx) - 48 'Convert "0" - "F" to 0-F If CL > 10 Then CL -= 7 Dim CR As Integer = Src(Idx + 1) - 48 'do the same for the right hex digit If CR > 10 Then CR -= 7 If Index > UBound(Result) Then ReDim Preserve Result(Index + 255) 'bump by 256 (allow for Index offset) End If Result(Index) = CByte(CL * 16 + CR) 'stuff byte value Index += 1 'bump index Next ReDim Preserve Result(Index - 1) 'set array to final size Return Result 'return the final result End Function

Like with the DecodeBase64ToBytes() method, mentioned earilier, you can use the returned array to save

to a file, or render it to an image, if that is its purpose. However, if you would like to convert to text, if that

was its origin, then you can convert the returned Byte array like this: Dim NewStr As String = Encoding.UTF8.GetChars(DecodeBinHex(strData))

Page 27: Send (SMTP) and Retrieve (POP3) Email with Ease under VB.NET

–27–

PART THREE

Receiving Email Under VB.NET using Native Methods

This third part of this article is to read email into VB.NET. We are going to do this with ease, because none

of the methods I present here are either long or complicated. Once you examine all the methods that we will

use to define our email reading class, POP3, you may really start to wonder why Microsoft did not choose to

make POP3 processing as easily accessible as SMTP processing. Perhaps they figured, and maybe rightly so,

that if anyone can write an email processor that supports the RFC 2045 specification, then they likely had sufficient knowledge to easily handle TCP Clients without an easier front end.

There are hundreds of thousands of programmer queries on the internet asking for how access POP3

from VB.NET (and those are just the ones who took time out of their day to say they needed POP3 front

end code). Most “solutions” simply direct you to a few samples of comment-challenged C# or C++

code. Most VB.NET gurus will simply say that POP3 is impossible to do under VB.NET.

But because it was impossible, it took me all afternoon to come up with my own VB.NET solution, and it

works better than any C# or C++ version I have yet seen. I must admit that after having fully developed this

solution, I finally did find a solitary VB.NET article, last updated on 26 March, 2003, on MSDN by Duncan Mackenzie (see “Checking My E-mail” at http://msdn.microsoft.com/en-us/library/ms973213.aspx), who is the Microsoft Visual Basic .NET Content Strategist for MSDN, which implements a very simple sample solution. His

example approach was very rudimentary (I almost said basic – no pun intended), slow, it did not provide for

SSL certificates, it featured little functionality, and it used a lot of code where I needed only a little just to

accomplish the same thing. But I am not knocking him; he was writing this for VB2002, and he was just

throwing out an example, not a full implementation. Plus, working with the .NET Framework is much easier

now than it was back in the wild and wooly days of ‘ot 3 or even ‘ot 1.

My solution was inspired by C# code pieced together by Randy Charles Morin, author of the

KBCafe.com blog site (see “How to POP3 in C#” www.kbcafe.com/articles/HowTo.POP3.CSharp.pdf, circa May

2002, but my current link is to a recently updated version of the article). Randy sifted various C#

solutions and tacked together his own. Bill Dean also made a 5 August 2003 C# submission to The Code

Project (www.codeproject.com/KB/IP/pop3client.aspx), which was inspired by Agus Kurniawan’s article on using

C# to communicate with a POP3 server, submitted to The Code Project on 19 January, 2002

(www.codeproject.com/KB/IP/popapp.aspx).

For all those listed, their code interfaces are very simplistic, and even rougher around the edges than that

of Mackenzie’s work. For example, not one of them could access SSL/TLS accounts, and interpreting

email was not addressed (yep, you are going to find a utility here to easily break emails up into its parts).

My initial goal was simply to see why those internet gurus, some of whom, based on their writing skills,

I can easily imagine sitting there in 3-day-old boxers, scarfing down a bag of M&Ms and flushing the

sugar away with Diet Pepsi, were saying that this kind of solution was not possible under VB.NET.

After building a VB.NET version familiarly modeled after Randy’s approach, I then took great pains to

significantly optimize the code for both size and speed, and to address limitations he pointed out (which

resulted in two complete rewrites of my code), and further enhancing it to accommodate many more

functions to conform more fully to the POP3 specifications (see “Post Office Protocol – Version 3” at

www.ietf.org/rfc/rfc1939.txt), to include optional TCP Ports and SSL/TLS support. I finally fully commented the

code. By the way, what is it with most developers and their inability to comment their code? Do comments

have cooties?

NOTE: Cooties was a term first used in a 1917 Service Manual, and is likely to have come from the Malayan word kutu, meaning lice. The trenches of World War I were a veritable breeding ground for every kind of parasitic pest imaginable.

We will create a single file, but we will include 3 classes. The file will be a class module. So go ahead

and create a class file named POP3.vb. This shall result in the following shell code being generated:

Page 28: Send (SMTP) and Retrieve (POP3) Email with Ease under VB.NET

–28–

Public Class POP3 End Class

We will leave this alone for now and create our other two simple classes below it.

The second class, named POP3Message, shall define a message class, which will contain each email

message. It will only hold fields, much like its abstract class cousin, a Structure, but we will define it as

an object to avoid Boxing as we work with it (Boxing is the process of wrapping a structure within a class shell so that it can be operated on as a reference object, rather than as a value object. This process has a tiny price in time, but is more work than we require). It is declared below:

'------------------------------------------------------------------------------- ' Class Name : POP3Message ' Purpose : POP3 message data '------------------------------------------------------------------------------- Public Class POP3Message Public MailID As Integer = 0 'message number Public ByteCount As Integer = 0 'length of message in bytes Public Retrieved As Boolean = False 'flag indicating if the message has been retrieved Public Message As String = vbNullString 'the text of the message Public Overrides Function ToString() As String Return Message End Function End Class

NOTE: Do not be nervous about adding more than one class to a file. This is perfectly acceptable. In fact, if these other classes were used only within the POP3 class, we could have embedded them within the body of that class, at the same level as its methods. In fact, you might be able to see how a .NET Namespace is structured from this. You will see this concept much more clearly once we put these classes together to create a class library, which we will be able to import into our applications.

As you can see, the above class object will store the message number as an integer. This is the reference

number that the email server assigns to that message, which is normally an incremental index, but we

should not make any assumptions about it. It also has a field storing the messages size in bytes. A

Boolean flag acts to indicate if we have actually retrieved the message text, or just its references, and

then we store the message itself as a string. Because email is transmitted as text, even HTML and Rich

Text format, and binary attachments are tacked on as encoded 7-bit ASCII strings, text is usually

formatted to certain line widths, and each line is terminated by a Carriage Return and Linefeed (vbCrLf).

The email program usually translates this encoding and displays any formatted representation within a

text box, rich text box, or web control.

Our third class, POP3Exception, is simply an error class, so that we can differentiate POP3 errors from

coding errors, so we can detect it in a Try…Catch Block, such as by using “Catch e As POP3Exception”:

'------------------------------------------------------------------------------- ' Class Name : POP3Exception ' Purpose : process exception ' NOTE : This is a normal exception, but we wrap it to give it an identy ' : that can be associated with our POP3 class '------------------------------------------------------------------------------- Public Class POP3Exception Inherits ApplicationException Public Sub New(ByVal str As String) MyBase.New(str) End Sub End Class

It may not look like much, but it holds just as much capability and functionality as any other Exception

object, plus we can trap errors that we will know were issued by it. That is the real beauty of inheritance.

Now we are ready to construct our main class, POP3.

To do everything I want to do in this class, I already know that I will need to import 2 namespaces:

1. I will first import System.Net, from which I can access the Sockets and Security namespaces. The Sockets namespace is important because through it I will be able to access a POP3 server though a TC/IP Client class. The Security namespace is important if we will require SSL/TLS authentication, such as Gmail and many others requires.

2. The second namespace to import is System.Text, because I will need to translate between Unicode strings and Byte arrays. Although it is possible to long-path these namespaces in the code without a compilation cost of even 1 byte, I like to list the namespaces used at the top for self-documentation purposes, to inform reviewers of my use of resources.

Page 29: Send (SMTP) and Retrieve (POP3) Email with Ease under VB.NET

–29–

I will also need to have local fields that will live as long as the class instance. Among those I will need a

Boolean flag that will indicate if we will require SSL authentication or not. We will also need to store

two stream objects; one for a standard non-SSL Network Stream, and a possible second SSL Stream.

Both of these streams shall be used for POP3 mail server communication. Regardless, the Network

Stream is essential for both types, because an SSL piggybacks itself onto a Network Stream. Finally, I

want to store the last line read from the server in case we ever want to check it further.

Therefore, the main body of my file, with all class bodies, becomes the following:

Option Strict On Option Explicit On '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ' POP3 - Copyright 2011, 2012 © by David Ross Goben. '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 'This VB.NET Code was inspired by C# code originally written Randy Charles Morin, 'author of KBCafe.com. ' ' I have optimized the heck out of the code to speed I/O and program execution, ' forcing a complete rewrite, I have added MANY language and POP3 enhancements, ' cleaned up a lot of clutter, and added Port and SSL support. ' Oh! And I include REAL comments. '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Imports System.Net, System.Text '------------------------------------------------------------------------------- ' Class Name : POP3 ' Purpose : POP3 Interface Class '------------------------------------------------------------------------------- Public Class POP3 Inherits Sockets.TcpClient 'this class shall inherit all the functionality of a TC/IP Client Dim Stream As Sockets.NetworkStream 'non-SSL stream object Dim UsesSSL As Boolean = False 'True if SLL authentication required Dim SslStream As Security.SslStream 'set to SSL stream supporting SSL authentication if UsesSSL is True Dim SslStreamDisposed As Boolean = False 'true if we disposed of the SSL Stream object Public LastLineRead As String = vbNullString 'copy of the last response line read from the TCP server '>>>>>>>>>ANY MORE CODE WILL BE INSERTED HERE HERE<<<<<<<<< End Class '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ '------------------------------------------------------------------------------- ' Class Name : POP3Message ' Purpose : POP3 message data '------------------------------------------------------------------------------- Public Class POP3Message Public number As Integer = 0 'message number Public bytes As Integer = 0 'length of message in bytes Public retrieved As Boolean = False 'flag indicating if the message has be retrieved Public message As String = vbNullString 'the text of the message End Class '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ '------------------------------------------------------------------------------- ' Class Name : POP3Exception ' Purpose : process exception ' NOTE : This is a normal exception, but we wrap it to give it an identy ' : that can be associated with clsPOP3 '------------------------------------------------------------------------------- Public Class POP3Exception Inherits ApplicationException Public Sub New(ByVal str As String) MyBase.New(str) End Sub End Class

The first and last thing we need to do after defining this is to connect to the POP3 server, and of course,

to disconnect from it when we are finished. Plus, we may as well find out if we have any email.

Page 30: Send (SMTP) and Retrieve (POP3) Email with Ease under VB.NET

–30–

Connecting to a POP3 Server Connecting to the server involves making a connection to the server, typically through generic Port 110,

but we must keep it open for other ports in case they are defined (note that email is now trending toward

SSL/TLS-encrypted port 995). We then have to submit our email Username and Password to it. We will

also want to save the server name off in case we will require later SSL authentication.

Connect()is a method that is built into the socket library, so we will have to overload it to add our own

customizations on top of it. This is not as scary as it sounds, because we can still access the original

method declared for the TCPClient base class that our POP3 class inherited from, which is a good thing,

because we will in fact need to invoke it.

Connecting to the POP3 Host Server is always the first thing we do. Once we have done that, we will be

able to issue all other POP3 commands, until we either remove our connection to the POP3 class, which

breaks our connection to the server, or we can explicitly disconnect from the server. We must have this

choice in terminating POP3 server communication, as you will learn, in order to provide us with the

ability to remove or not removed downloaded emails from the Host Server.

We will also require a method to submit queries to the server, which will also mean that we need a

resource to check responses from the server (its replies to our queries). Communication with the server

is just a long series of our requests and its responses. We will examine each of these parts in their turn.

Following is the Connect() method, which references other methods that will be added shortly:

'******************************************************************************* ' Sub Name : Connect (This is the first the we do with a POP3 object) ' Purpose : Connect to the server using the Server, User Name, Password, ' : and a flag indicating if SSL authentication is required ' : ' Returns : Nothing ' : ' Typical TelNet I/O: 'telnet mail.domain.net 110 (submit) '+OK POP3 mail.domain.net v2011.83 server ready 'USER myusername (submit) '+OK User name accepted, password please 'PASS mysecretpassword (submit) '+OK Mailbox open, 3 messages (the server locks and opens the appropriate maildrop) '******************************************************************************* Public Overloads Sub Connect(ByVal Server As String, _ ByVal Username As String, _ ByVal Password As String, _ Optional ByVal InPort As Integer = 110, _ Optional ByVal UseSSL As Boolean = False) If Connected Then Disconnect() ' 'check underlying boolean flag to see if we are presently connected, 'and if so, disconnect that session UsesSSL = UseSSL 'set flag True or False for SSL authentication MyBase.Connect(Server, InPort) 'now connect to the server via our base class Stream = MyBase.GetStream 'before we can check for a response, we first have to set up a non-SSL stream If UsesSSL Then 'do we also need to use SSL authentication? SslStream = _ New Security.SslStream(Stream) 'yes, so build an SSL stream object on top of the non-SSL Network Stream SslStream.AuthenticateAsClient(Server) 'add authentication as a client to the server End If If Not CheckResponse() Then Exit Sub ' 'exit if an error was encountered If CBool(Len(Username)) Then 'if the username is defined (some servers will reject submissions) Me.Submit("USER " & Username & vbCrLf) 'submit user name If Not CheckResponse() Then Exit Sub ' 'exit if an error was encountered End If If CBool(Len(Password)) Then 'if the password is defined (some servers will reject submissions) Me.Submit("PASS " & Password & vbCrLf) 'submit password If Not CheckResponse() Then Exit Sub ' 'exit if an error was encountered End If End Sub

As you can see, we begin out adventure by initiating communication with the POP3 server. We do this

by providing the Connect() method the host server address, our email username (or our whole email

address, if your particular server requires it), our email password, a port number if it differs from TCP

Port 110, which is becoming common, and True if our POP3 server will require a Secure Socket Layer.

Page 31: Send (SMTP) and Retrieve (POP3) Email with Ease under VB.NET

–31–

The first thing that Connect() does is check to see if we have a valid POP3 connection. After that, we

stow away important references, such as our SSL choice. If we will use SSL, we will set our UsesSSL

flag to True.

Next, we access the base class’s Connect() method and submit the server name we wish to access, such

as “mail.comcast.net” or “pop.gmail.com”. By default, we submit Port 110 for communication. This is

the port most often used by mail servers, though encrypted SSL/TLS connections are growing in

popularity. Some servers use a different port, but this is usually because they also use SSL

Authentication. Gmail does this. For example, it uses a secure TCP Port 995 for POP3.

NOTE: Some servers let anyone and everyone on. In such cases, some few servers sometimes do not even accept usernames and/or passwords and will reject them (or accept any and all responses), or allow you to use a generic response, such as “guest” or “user” and “password”. In those cases, we have to allow for that.

Checking for a POP3 Server Response We next invoke CheckResponse() to see if a request was accepted. If so, a returned string begins with

“+OK”. Because this test runs rampant throughout a TCP client session, I broken out into a separate function

that returns True if it was successful (the response, stored in our public string, LastLineRead, begins with

“+OK”), and False if it was not (an error begins with “-ERR”). This test follows most other submissions.

Here is the CheckResponse() method:

'******************************************************************************* ' Function Name : CheckResponse ' Purpose : Check the response to a POP3 command ' : ' Returns : Boolean flag. True = Success, False = Failure ' : ' NOTE : All status responses from the server begin with: ' : +OK (OK; Success, or request granted) ' or : -ERR (NAGATIVE; error) '******************************************************************************* Public Function CheckResponse() As Boolean If Not IsConnected() Then Return False ' 'exit if not in TRANSACTION mode LastLineRead = Me.Response 'check response (and save response line) If (Left(LastLineRead, 3) <> "+OK") Then 'OK? Throw New POP3Exception(LastLineRead) 'no, so throw an exception Return False 'return failure flag End If Return True 'else return success flag End Function

This method in turn invokes the Response() method. But what is important here is that we get our first

look at returned data from the POP3 server, and if we encounter an error, such as the first 3 letters of the

response line not being “+OK”, then we throw our POP3Exception error, transmitting the returned text

line to the message pump hidden with the exception object. You may even want to preview what the full

responses are by examining the LastLineRead text; the rest of the “+OK” message can be informative

(as are error messages, which all start with “-ERR”), such as “+OK Gpop ready for requests from

12.34.567.89 fahrvergnugen8.0”.

Checking for Being Connected to a POP3 Server

The IsConnected() method consolidates a lot of checks to the underlying base class’s Boolean

Connected property. It throws a POP3ExceptionError if the server is not connected, indicating that it is

not in a Transaction state:

'******************************************************************************* ' Function Name : IsConnected ' Purpose : Return connected to Server state, throw error if not ' : ' Returns : Boolean Flag. True if connected to server ' : '******************************************************************************* Public Function IsConnected() As Boolean If Not Connected Then 'if not connected, throw an exception Throw New POP3Exception("Not Connected to an POP3 Server.") Return False 'return failure flag End If Return True 'Indicate that we are in the TRANSACTION state) End Function

Page 32: Send (SMTP) and Retrieve (POP3) Email with Ease under VB.NET

–32–

Getting a Response from the POP3 Server

The Response() method is where all the really interesting things happen. Here we will finally

implement our communication streams to receive “real” information from the POP3 server.

'******************************************************************************* ' Function Name : Response ' Purpose : get response from server (read from the mail stream into a buffer) ' : ' Returns : string of data from the server ' : ' NOTE : If a dataSize value > 1 is supplied, then those number of bytes will be streamed in. ' : Otherwise, the data will be read in a line at a time, and end with the line end code (Linefeed (vbLf) 10 decimal) '******************************************************************************* Public Function Response(Optional ByVal dataSize As Integer = 1) As String Dim enc As New ASCIIEncoding 'medium for ASCII representation of Unicode characters Dim ServerBufr() As Byte 'establish buffer Dim Index As Integer = 0 'init server buffer index and character counter If dataSize > 1 Then 'did invoker specify a data length to read? '------------------------------------------------------- ReDim ServerBufr(dataSize - 1) 'size to dataSize to read as a single stream block (allow for 0 index) Dim dtsz As Integer = dataSize Dim sz As Integer 'variable to store actual number of bytes read from the stream Do While Index < dataSize 'while we have not read the entire message... If UsesSSL Then 'process through SSL Stream if secure stream sz = SslStream.Read(ServerBufr, Index, dtsz) 'read a server-defined block of data from SSLstream Else 'else process through general TCP Stream sz = Stream.Read(ServerBufr, Index, dtsz) 'read a server-defined block of data from Network Stream End If If sz = 0 Then Return vbNullString ' 'we lost data, so we could not read the string Index += sz 'bump index for data count actually read dtsz -= sz 'drop amount left in buffer Loop Else '------------------------------------------------------ ReDim ServerBufr(255) 'initially dimension buffer to 256 bytes (including 0 offset) Do If UsesSSL Then 'process through SSL Stream if secure stream ServerBufr(Index) = CByte(SslStream.ReadByte) 'read a byte from SSLstream Else 'else process through general TCP Stream ServerBufr(Index) = CByte(Stream.ReadByte) 'read a byte from Network stream End If If ServerBufr(Index) = -1 Then Exit Do ' 'end of stream if -1 encountered Index += 1 'bump our offset index and counter If ServerBufr(Index - 1) = 10 Then Exit Do ' 'done with line if Newline code (10; Linefeed) read in If Index > UBound(ServerBufr) Then 'if the index points past end of buffer... ReDim Preserve ServerBufr(Index + 256) 'then bump buffer another 256 bytes, but keep existing data End If Loop 'loop until line read in End If Return enc.GetString(ServerBufr, 0, Index) 'decode from a byte array into a string and return the string End Function

Here we read from either the Network Stream (Stream) or the SSL Stream (SslStream), depending on

whether we require SSL authentication to communicate with the TCP Server or not. If we do not specify

a buffer size, we read the port one byte at a time until we encounter an end of line code (vbLf ; 10).

Every C#/C++ example I have ever seen first reads a stream byte into a single-element intermediate buffer

array using the stream.Read() method, which requires a destination byte array. However, I have noticed

that this can hang until an eventual timeout (usually 30-60 seconds) if the signal drops off, so we end up

trying to read beyond the end of the provided stream, which is a more concerning issue when reading one

byte at a time. Unlike everyone else, I chose the ReadByte() method instead, which can read to a Byte

variable (though I also chose to read it directly into the ServerBufr Byte array instead), but more importantly,

it will return a -1 code immediately if we moved beyond the end of the stream or if the connection drops off.

If the Stream.ReadByte() method or the SslStream.ReadByte() methods do not report back that they

failed to read from their stream, then the index/counter is incremented.

We loop until we get an end of a line code (a value of 10, representing a Linefeed character; vbLf,

typically following a Carriage Return; vbCr), or if no data was received (it could mean that the signal

dropped off). Once it is done reading bytes, the byte array is then translated to a Unicode string (the

default string format for VB.NET) and it is returned to the invoker.

If a buffer size was supplied, the ServerBufr is immediately dimensioned to that size (-1 is applied for the 0

offset). We then continuously try to fill the buffer full. However, the server actually returns data to us in

blocks, not in one big blobby mess of data. For example, the “header” of an email actually consists of a

number of headers, and the body of an email is considered another block, as are attachments and alternate

Page 33: Send (SMTP) and Retrieve (POP3) Email with Ease under VB.NET

–33–

views. And even then, if the data block is longer than the server’s stream buffer, it will break it up into a

sequence of sub-blocks. Documentation says that the default maximum stream buffer size is 998 bytes,

though it is not uncommon to find buffers that are larger, such as 1400 bytes. This size is implementation-

defined, even though our Stream and SslStream objects do have a SetLength property that is supposed to

allow you to set the stream buffer size, this is not yet supported: a sad fact the documentation also states.

Submitting a Request to the POP3 Server

The one thing left that we must look at, which is so far unresolved, is the Submit() method:

'******************************************************************************* ' Sub Name : Submit ' Purpose : Submit a request to the server ' : ' Returns : Nothing ' : ' NOTE : Command name must be in UPPERCASE, such as "PASS pw1Smorf". ' : "pass pw1Smorf" would not be acceptable, though some few servers do allow for this, we should never assume it. '******************************************************************************* Public Sub Submit(ByVal message As String) Dim enc As New ASCIIEncoding 'medium for ASCII representation of Unicode characters Dim WriteBuffer() As Byte = enc.GetBytes(message) 'converts the submitted string into to a sequence of bytes If UsesSSL Then 'using SSL authentication? SslStream.Write(WriteBuffer, 0, WriteBuffer.Length) 'yes, so write SSL buffer using the SslStream object Else Stream.Write(WriteBuffer, 0, WriteBuffer.Length) 'else write to Network buffer using the non-SSL object End If End Sub

We convert our Unicode request to a Byte array, and then send it to the appropriate stream, depending

on whether we use SSL authentication or not. One thing you may not realize is that we can directly

access this method and submit a command directly. Just be sure that the command word is uppercase.

Disconnecting from the POP3 Server

A final primary thing that we may need to do, if we want the server to enter its usual UPDATE state, is

to disconnect from the POP3 Server. That is accomplished through the Disconnect() method:

'******************************************************************************* ' Sub Name : Disconnect (This is the last the we do with a POP3 object) ' Purpose : Disconnect from the server and have it enter the UPDATE mode ' : ' Returns : Nothing ' : ' Typical telNet I/O: 'QUIT (submit) '+OK Sayonara ' ' NOTE: When the client issues the QUIT command from the TRANSACTION state, ' the POP3 session enters the UPDATE state. (Note that if the client ' issues the QUIT command from the AUTHORIZATION state, the POP3 ' session terminates but does NOT enter the UPDATE state.) ' ' If a session terminates for some reason other than a client-issued ' QUIT command, the POP3 session does NOT enter the UPDATE state and ' MUST not remove any messages from the maildrop. ' ' The POP3 server removes all messages marked as deleted from the ' maildrop and replies as to the status of this operation. If there ' is an error, such as a resource shortage, encountered while removing ' messages, the maildrop may result in having some or none of the ' messages marked as deleted be removed. In no case may the server ' remove any messages not marked as deleted. ' ' Whether the removal was successful or not, the server then releases ' any exclusive-access lock on the maildrop and closes the TCP connection. '******************************************************************************* Public Sub Disconnect() Me.Submit("QUIT" & vbCrLf) 'submit quit request CheckResponse() 'check response If UsesSSL Then 'SSL authentication used? SslStream.Dispose() 'dispose of created SSL stream object if so SslStreamDisposed = True End If End Sub

We check for a response, but we throw it away, at least at this level, because we are leaving, regardless.

Also, because we had to instantiate our SSL Stream (but only if we require SSL authentication), we

dispose of this resource. We do not have to concern ourselves with the Network Stream object, because

we only maintained a pointer to it. The actual Stream object is taken care of by the underlying

TCPClient base class, which will dispose of that object for us (we did not instantiate the Network

Stream object with “New”, because it was already instantiated by the underlying code of the base class).

Page 34: Send (SMTP) and Retrieve (POP3) Email with Ease under VB.NET

–34–

Of course, now that we can connect, log on, and disconnect from the POP3 server, it would also be a

really good idea to be able to read some data from it, such as an email maybe, before we disconnect.

Getting Email Statistics from the POP3 Server

The first thing we may want to do after connecting is to get some statistics, such as how many emails we

have in our mail drop on the server, and how many bytes this data occupies on the server. These can be

both served with our Statistics() method:

'******************************************************************************* ' Function Name : Statistics ' Purpose : Get the number of email messages and the total size as any integer array ' : ' Returns : 2-selement interger array. ' : Element(0) is the number of user email messages on the server ' : Element(1) is the total bytes of all messages taken up on the server ' : ' Typical telNet I/O: 'STAT (submit) '+OK 3 16487 (3 records (emails/messages) totaling 16487 bytes (octets)) '******************************************************************************* Public Function Statistics() As Integer() If Not IsConnected() Then Return Nothing ' 'exit if not in TRANSACTION mode Me.Submit("STAT" & vbCrLf) 'submit Statistics request LastLineRead = Me.Response 'check response If (Left(LastLineRead, 3) <> "+OK") Then 'OK? Throw New POP3Exception(LastLineRead) 'no, so throw an exception Return Nothing 'return failure flag End If Dim msgInfo() As String = Split(LastLineRead, " "c) 'separate by spaces, which divide its fields Dim Result(1) As Integer Result(0) = Integer.Parse(msgInfo(1)) 'get the number of emails Result(1) = Integer.Parse(msgInfo(2)) 'get the size of the email messages Return Result End Function

This returns an integer array. Result(0) will contain the number of email messages residing in your mail

drop, and Result(1) contains the total number of bytes the messages occupy on the server.

NOTE: Most online servers are Unix, and store line terminators as a vbLf, rather than vbCrLf, so you should expect a slight discrepancy when you load files, because your TCP connection will read and convert a lone vbLf to vbCrLf.

Getting an Email Reference List from the POP3 Server

The next thing we may want to do after connecting is to get a list of emails that are available on the

server. Our List() method takes care of this, constructing a ListArray of POP3Message objects from

the server, and then returning them to the invoker:

'******************************************************************************* ' Function Name : List ' Purpose : Get the drop listing from the maildrop ' : ' Returns : Any Arraylist of POP3Message objects ' : ' Typical telNet I/O: 'LIST (submit) '+OK Mailbox scan listing follows '1 2532 (record index and size in bytes) '2 1610 '3 12345 '. (end of records terminator) '******************************************************************************* Public Function List() As ArrayList If Not IsConnected() Then Return Nothing ' 'exit if not in TRANSACTION mode Me.Submit("LIST" & vbCrLf) 'submit List request If Not CheckResponse() Then Return Nothing ' 'check for a response, but if an error, return nothing ' 'get a list of emails waiting on the server for the authenticated user ' Dim retval As New ArrayList 'set aside message list storage Do Dim response As String = Me.Response 'check response If (response = "." & vbCrLf) Then 'done with list? Exit Do 'yes End If Dim msg As New POP3Message 'establish a new message Dim msgInfo() As String = Split(response, " "c) 'separate by spaces, which divide its fields msg.number = Integer.Parse(msgInfo(0)) 'get the list item number msg.bytes = Integer.Parse(msgInfo(1)) 'get the size of the email message msg.retrieved = False 'indicate its message body is not yet retreived retval.Add(msg) 'add a new entry into the retrieval list Loop Return retval 'return the list End Function

Page 35: Send (SMTP) and Retrieve (POP3) Email with Ease under VB.NET

–35–

The List() method constructs a list of POP3Message objects, but you may notice that we do not fill

their Message field with anything, just their server reference number and the number of bytes that the

message occupies. That is because when we submit a “List” request, the server returns a simple string

for each email that it has pending for the Username. This simple string consists of two numeric text

values, separated by a space, such as “1 2532”. The first is the reference index for each item in its mail

drop for the Username, and the other is the number of bytes the message occupies on the server.

There are also a number of peripheral POP3 commands that we can exploit to round out our email

retriever, such as retrieving the actual email message text (gee, what a unique idea), deleting an email

from the server, resetting any deletions (great for OOPS! situations), and we can even grab just the

header of the email and optionally a specified number of additional lines from the body of the email

(handy for previews). These peripheral methods will be listed one at a time, below.

Get an Email Header from the POP3 Server

'******************************************************************************* ' Function Name : GetHeader ' Purpose : Grab the email header and optionally a number of lines from the body ' : ' Returns : Gets the Email header of the selected email. If an integer value is ' : provided, that number of body lines will be returned. The returned ' : object is the submitted POP3Message. ' : ' Typical telNet I/O: 'TOP 1 0 (submit request for record 1's message header only, 0=no lines of body) '+OK Top of message follows ' xxxxx (header for current record is transmitted) '. (end of record terminator) ' 'TOP 1 10 (submit request for record 1's message header plus 10 lines of body data) '+OK Top of message follows ' xxxxx (header for current record is transmitted) ' xxxxx (first 10 lines of body) '. (end of record terminator) '******************************************************************************* Public Function GetHeader(ByRef msg As POP3Message, Optional ByVal BodyLines As Integer = 0) As POP3Message If Not IsConnected() Then Return Nothing ' 'exit if not in TRANSACTION mode Me.Submit("TOP " & msg.number.ToString & " " & BodyLines.ToString & vbCrLf) If Not CheckResponse() Then Return Nothing ''check for a response, but if an error, return nothing msg.message = vbNullString 'erase current contents of the message, if any ' 'now process message data by binding the lines into a single string ' Do Dim response As String = Me.Response 'grab message line If response = "." & vbCrLf Then 'end of data? Exit Do 'yes, done with the loop if so End If msg.message &= response 'else build message by appending the new line Loop Return msg 'return new filled Message object End Function

The GetHeader()method allows you to grab only the header of the message, or the header plus a

specified number of body text lines. By submitting a POP3Message to the GetHeader() function, this

method will return a pointer to the POP3Message object that you had submitted, containing the header

of the message. If it returns Nothing, then there was an error in trying to retrieve the header.

The Header of email is interesting, except to mere mortals, who have no use for it, most thinking it

contains nothing but routing data. However, though they may not realize it, the last few lines of the

header actually do have some interest to them. Consider the following trailing end of an email header:

From: [email protected] To: [email protected] Date: 25 Feb 2011 17:59:01 -0500 Subject: Test Content-Type: multipart/alternative; boundary=--boundary_0_570b636e-3a77-40a7-bf02-5b178a2cff5b

The lines beginning with the fields “From:”, “To:”, and “Subject:” are easily extractable. The “Date:”

field displays the Date and Time as local, with an offset value to GMT (Greenwich Mean Time;

originally referring to mean solar time at the Royal Observatory, Greenwich in England. It is now often

used to refer to Coordinated Universal Time (UTC)). But regardless, the above time means that now was

relatively 5 hours ago in Greenwich, England).

Page 36: Send (SMTP) and Retrieve (POP3) Email with Ease under VB.NET

–36–

You can also grab the Content-Type and, if multipart/alternative, which it will always report if there are

alternative views or attachments included, it will also include a “boundary=” parameter, which informs

you of what the boundary between the various parts will consist of. If no alternative views or

attachments, then the Content-Type will contain the type of data, such as text/plain, and maybe a

parameter containing character set used for the message. Alternative views and attachments, after a

boundary marker, also have a Content-Type field, which will informs you of the format the data is

formatted as, after decoding, and if an attachment, a name parameter that will tell you the default

filename (name) to store the data under, if the user wants to save the attachment to disc.

Retrieve an Email from the POP3 Server

'******************************************************************************* ' Function Name : Retrieve ' Purpose : Retrieve email from POP3 server for the provided POP3Message object ' : ' Returns : The submitted POP3 Message object with its Message property filled, ' : and its ByteCount property properly fitted to the message size. ' : ' NOTE : Some email servers are set up to automatically delete an email once ' : it is retrieved from the server. Outlook, Outlook Express, and ' : Windows Mail do this. It is an option under Juno and Gmail. So, if we ' : do not submit a POP3 QUIT (the Disconnect() method), but just close ' : out the POP3 object, the message(s) will not be deleted. ' : Even so, most Windows-based server-processors will add an additional ' : CR for each LF, but the reported email size does not account for them. ' : So we must retreive more data to account for this. ' ' Typical telNet I/O: 'RETR 1 (submit request to retrive record index 1 (cannot be an index marked for deletion)) '+OK 2532 octets (an octet is a fancy term for a 8-bit byte) ' xxxx (message header and message are retreived) '. (end of record terminator) '******************************************************************************* Public Function Retrieve(ByRef msg As POP3Message) As POP3Message If Not IsConnected() Then Return Nothing ' 'exit if not in TRANSACTION mode Me.Submit("RETR " & msg.number.ToString & vbCrLf) 'issue request for indicated message number If Not CheckResponse() Then Return Nothing ' 'check for a response, but if an error, return nothing msg.message = Me.Response(msg.bytes) 'grab message line 'the stream reader automatically convers the NewLine code, vbLf, to vbCrLf, so the files is not yet 'fully read. For example, a files that was 233 lines will therefore have 233 more characters not 'yet read from the files when it has reached its reported data size. So we will scan these in. 'But even if this was not the case, the trailing "." & vbCrLf is still pending... Do Dim S As String = Response() 'grab more data If S = "." & vbCrLf Then 'end of data? Exit Do 'If so, then exit loop End If msg.message &= S 'else tack data to end of message Loop 'keep trying msg.bytes = Len(msg.message) 'ensure full size updated Return msg 'return new message object End Function

By submitting a POP3Message to the Retrieve() function, presumably its present message string

being empty or ignorable, it will fill its Message property with the entire email, including both the full

header and body of the message. It will also return a pointer to the message, except if there was a failure.

Deleting an Email from the POP3 Server

'******************************************************************************* ' Sub Name : Delete ' Purpose : Delete an email ' : ' Returns : Nothing ' : ' NOTE : Some email servers are set up to automatically delete an email once ' : it is retrieved from the server. Outlook, Outlook Express, and ' : Windows Mail do this. It is an option under Juno and Gmail. ' ' Typical telNet I/O: 'DELE 1 (submit request to delete record index 1) '+OK Message deleted '******************************************************************************* Public Sub Delete(ByVal msgHdr As POP3Message) If Not IsConnected() Then Exit Sub ' 'exit if not in TRANSACTION mode Me.Submit("DELE " & msgHdr.number.ToString & vbCrLf) 'submit Delete request CheckResponse() 'check response End Sub

The Delete() method will delete a specified message. Just provide the selected POP3Message object.

Page 37: Send (SMTP) and Retrieve (POP3) Email with Ease under VB.NET

–37–

Reset (Undo) All Deletes from the POP3 Server

'******************************************************************************* ' Sub Name : Reset ' Purpose : Reset any deletion (automatic or manual) of all email from ' : the current session. ' : ' Returns : Nothing ' : ' Typical telNet I/O: 'RSET (submit) '+OK Reset state '******************************************************************************* Public Sub Reset() If Not IsConnected() Then Exit Sub 'exit if not in TRANSACTION mode Me.Submit("RSET" & vbCrLf) 'submit Reset request CheckResponse() 'check response End Sub

The Reset() method will undelete emails that you selected with Delete() during the current session.

Send a ‘Keep-Alive’ NOOP Command to the POP3 Server

'******************************************************************************* ' Function Name : NOOP (No Operation) ' Purpose : Does nothing. Juts gets a position response from the server ' : ' Returns : Boolean flag. False if disconnected, else True if connected. ' : ' NOTE : This NO OPERATION command is useful when you have a server that ' : automatically disconnects after a certain idle period of activity. ' : This command can be issued by a timer that also monitors users ' : inactivity, and issues a NOOP to reset the server timer. ' : ' Typical telNet I/O: 'NOOP (submit) '+OK '******************************************************************************* Public Function NOOP() As Boolean If Not IsConnected() Then Return False 'exit if not in TRANSACTION mode Me.Submit("NOOP") Return CheckResponse() End Function

The NOOP() method will do nothing but obtain a response from the server. As noted, above, this method

can be used just to tickle the server. Often servers will disconnect from a user after a certain period of

time. But obtaining a response from the server, this timer is reset to the beginning of its counter, thus

keeping the connection alive. If you have such a server, you could maintain your own timer that times

out before the server time, and automatically submits a NOOP request to the server. Of course, you will

want to reset your own timer (Me.myTimer.Enabled=False: Me.myTimer.Enabled=True) each time you issue a

command to the server, to reset your own timer.

Disposing of Resources

'******************************************************************************* ' Function Name : Finalize ' Purpose : remove SSL Stream object if not removed '******************************************************************************* Protected Overrides Sub Finalize() If SslStream IsNot Nothing AndAlso Not SslStreamDisposed Then 'SSL Stream object Disposed? SslStream.Dispose() 'no, so do it End If MyBase.Finalize() 'then do normal finalization End Sub

Normally, when you issue a Disconnect() command, the resources are released. But you may not want

to always do that. Many email providers will delete the message from the server once you have retrieved

it, or they make it an option. However, if the server does not receive a QUIT command (issued by

Disconnect()), then it will not enter into its UPDATE state, where servers often delete retrieved

messages. If it does not enter the UPDATE state, then the emails will remain on the server.

However, if you do not issue a Disconnect() command, the created resources will not be removed.

This is not really a big issue, because the .NET Garbage Collector will detect that the resources are no

longer referenced and remove them, but it is always a good idea to take care of that. Hence, we added a

Finalize() method that will be issued by the Garbage Collector before destroying the POP3 object.

NOTE: You should never invoke the Finalize method yourself. Let the system automatically take care of that for you.

Page 38: Send (SMTP) and Retrieve (POP3) Email with Ease under VB.NET

–38–

Using the Completed POP3 Class Now that we have defined all the parts of our POP3 class (the complete class listing is at the end of this

article), we need to be able to use it.

Even though the following is a short-cut, consider invoking the following sample intermediary method,

during testing anyway, which will connect to the POP3 host server, grab all the emails, and disconnect.

However, during testing, as you prod and modify it to implement your own test designs, it might be a

good idea to leave your email on the server, to use for later testing. In that case, you should skip

invoking the Disconnect() method. No worries; once our objects go out of scope, or are set to Nothing,

the connection will be lost (if you are concerned about waiting for the Garbage Collector – do not waste

another bead of sweat. It runs as a separate thread in the background, and it runs much more often than

most people would imagine. Were if up to me, I would have it running constantly in the background).

The following sample method will return all the available online emails from the server to the invoker

as an ArrayList filled with all your Email Messages stored as POP3Message objects:

'******************************************************************************* ' Sub Name : ReadPOP3 ' Purpose : Samples method to Read a POP3 account maildrop ' NOTE : This method should be modified to suit your application. For ' : example, the text may be HTML or Rich Text format. The raw text ' : should be plugged into the appropriate medium, such as ' : "Me.RichTextBox1.Rtf = msg2.Message" for proper viewing. '******************************************************************************* Public Function ReadPOP3(ByVal Server As String, _ ByVal Username As String, _ ByVal Password As String, _ Optional ByVal InPort As Integer = 110, _ Optional ByVal UseSSL As Boolean = False) As ArrayList Try Dim InMail As New POP3 'create a new POP3 connection InMail.Connect(Server, Username, Password, InPort, UseSSL) 'Connect to user account '----------------------------------------------------------- Dim Stats() As Integer = InMail.Statistics() 'get maildrop statistics (number of message, total byte size) If Stats(0) = 0 Then 'check number of messages for being 0 (none) Return Nothing 'no email found in the maildrop, so nothing to do End If Dim localList As New ArrayList 'set up list of emails that will contain message text For Each msg As POP3Message In InMail.List 'parse each header object (contains only size and index) localList.Add(InMail.Retrieve(msg)) 'add a message object with message text Next 'process all messages '----------------------------------------------------------- InMail.Disconnect() 'disconnect from server (DISABLE THIS LINE TO KEEP EMAILS ON SERVER) Return localList 'return list of filled POP3Messages to invoker Catch e As POP3Exception 'POP3-side error MsgBox(e.Message, _ MsgBoxStyle.OkOnly Or MsgBoxStyle.Exclamation, _ "Error Encountered") Catch e As Exception 'general programming error MsgBox(e.Message, _ MsgBoxStyle.OkOnly Or MsgBoxStyle.Exclamation, _ "Error Encountered") End Try Return Nothing End Function

The first three parameters are mandatory, though in the rare cases of an all-are-welcome server, you may

need to provide a blank Username and/or Password. The Server is always mandatory, such as

“pop.gmail.com” or “authpop.juno.com”. Also, though on most log-ins, the Username is the part of the

user’s email address that comes before the “@” (At) sign, such as “Im1Idjut”, some require the entire email

address; most notably Gmail, which needs to have the entire email address, such as “[email protected]”.

The last two parameters are required only if your POP3 TCP Port is different or your server needs SSL

authentication. For example, the parameter list for a Verison account would be ("incoming.verizon.net",

"Im1Idjut", "CantRecall1"), specifying Server, Username, and Password. But on Comcast, the parameter list

would be ("mail.comcast.net", "Im1Idjut", "CantRecall1", 995, True).

The first thing we do is instantiate an instance of our POP3 class. We then connect to the server. Finally, we

build an ArrayList named localList that will contain full email messages. Once we complete that, we

disconnect from the server (but only do this when you are done with the connection and you want the server to enter the UPDATE mode), and then we return the list to the invoker, who can read them at their leisure.

Page 39: Send (SMTP) and Retrieve (POP3) Email with Ease under VB.NET

–39–

Suppose we have a routine supporting a button, cmdReceive, that will invoke the ReadPOP3() method,

and then loop through it, displaying each message in a message box (though rather than absolute

parameters, I can only hope that your POP3 applications will process variables or text box data):

Private Sub cmdReceive_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdReceive.Click Me.cmdReceive.Enabled = False 'disable Receive button Dim EmailBag As ArrayList = ReadPOP3("pop.gmail.com", "[email protected]", "YudLk2Knw", 995, True) If EmailBag IsNot Nothing AndAlso EmailBag.Count <> 0 Then 'if data was found For Each msg As POP3Message In EmailBag 'display each email Me.TextBox1.Text = msg.message 'stuff in the text box and display in a MsgBox MsgBox(msg.message, MsgBoxStyle.OkOnly, "Message # " & msg.MailID.ToString & " of " & EmailBag.Count.ToString) Next 'process all messages Else 'transfer here if one loves you MsgBox("No email found on server.", MsgBoxStyle.OkOnly Or MsgBoxStyle.Information, "No Email") End If Me.cmdReceive.Enabled = True 're-enable Receive button End Sub

This is all. You may want to design an interactive email program. In such cases you would want to keep a

connection open and allow the user to fiddle with their email to their heart’s content, such as giving them

time to read them, reply to them, forward the same jokes for the twenty-third time, though you would want to

plagiarize the really good ones. Using the methods provided in the POP3 class, you can do quite a bit with it.

PART FOUR

Email Data Blocks Made Easy There is a lot of information we can gather from just a little bit of data when it comes to an email.

Let’s first us look at a simple email and extract its important parts, and what we can do with them:

From: [email protected] FROM Field (FROM, TO, SUBJECT, and DATE Fields can be placed in any order within their 4-field zone). To: [email protected] TO Field. Date: 25 Feb 2011 19:07:02 -0500 DATE Field (always d mmm, yyyy HH:MM:SS zzz). Subject: Test SUBJECT Field. Content-Type: text/html; charset=us-ascii CONTENT-TYPE Field. The Message body data is Text and is formatted as HTML. Content-Transfer-Encoding: quoted-printable Content-Transfer-Encoding Field. Quoted-Printable means data is Hex-Tag encoded. This line, following Content-Transfer-Encoding, is ALWAYS blank. It is NOT part of the message. This is a test=0D=0AThis is a second test=0D=0A Data is here, until the end of the data, or until a boundary marker is encountered.

The above is just a simple email; the type that my QuickiEmail() or BrainDeadSimpleEmailSend() would mail out. There is of course other routine data located above it. But you may be wondering how

the data is formatted, or how to separate Alternate Views from Attachments, or how to decode them, or

what did you do that the boss volunteered you to develop an email interface?

Relax. It is all easy. To solve all these issues, we are soon going to add a single method to our library

that will break all this information out for you.

If you refer to RFC 2045 (www.ietf.org/rfc/rfc2045.txt), “Multipurpose Internet Mail Extensions, (MIME) Part

One: Format of Internet Message Bodies”, you will find all answers. Or, you could let me read them, and

then simply let me show you how to use them.

From our simple example, above, we can clearly see 6 fields defined: “From”, “To”, “Date”, Subject”,

“Content-Type” and “Content-Transfer-Encoding”. Each is followed by a colon “:” to delimit the end

of the field, separating it from its data. From, To, and Subject are easy enough. The Date field stores the

date and time as “d mmm, yyyy hh:mm:ss zzz”. That is, day, month, year, then hours, minutes, seconds,

followed by the Zulu-time offset (the number of hours against Greenwich, England time).

The data for a field can consist of one or more parameters. If there is more than one, they are separated

from each other by a semicolon “;”. A parameter ending in vbCrLf marks the end of the parameter(s).

Between parameters, intervening white space (non-displayable characters), such as spaces, tabs,

Carriage Returns, Linefeeds, and such, are not counted as data. If the line ends in a “;”, then the next

line is considered a continuation of the previous line. For example, you may often see this:

Content-Type: text/plan; ";" indicates more parameters defined, so this is not the end of the field parameters Name=API32.TXT continuations of previous lines are led by white space (non-printable characters: spaces, tabs, etc.)

The above is considered one line of data. It is clear that the next line is a continuation, because the

previous line ended in a semicolon, indicating another parameter is pending.

Page 40: Send (SMTP) and Retrieve (POP3) Email with Ease under VB.NET

–40–

You may also notice in message bodies and the like, something like this (part of my signature block):

This is a test line ended normally line consisted only of vbCrLf =20 line ended with blank space, so encoded line consisted only of vbCrLf =20 line ended with blank space, so encoded line consisted only of vbCrLf David Ross Goben \|/ ~ ~ (@ @)=20 a space preceded end of line, so encoded --oOO-_-OOo------------------------------------------------------------= non-breaking line continuation tag (=) ------- continuation of previous line --- data simply chopped off due to my 80-char plain text limit Author of "A Gnostic Cycle: Exploring the Origin of Christianity"

Notice the “=20” Hex-Tags. These represent spaces prior to an end of line. This means that between the

text and my signature are 4 lines, two of which have a space in them. Notice that there was a space down

by “@)”, as well. This can all be easily cleaned up by the DecodeQuotedPrintable() method.

Notice on the long, dashed line that at 73 characters, there was an email-impose end of line, tagged by

“=”, and followed by a vbCrLf. What this means is that when this message is processed, the line will

actually continue on the next line, where we find 7 more dashes. This equals an 80-character line once

we strip off the “=” and vbCrLf. The final 3 dashes are lost in the translation, being that I have set the

line limit of plain text data set at 80 characters in Outlook, so we have to live with this overflow.

Now, consider the following abbreviated email (it was actually quite long, even with a small image

(302x244), but it contains some interesting differences from previous raw emails we have examined):

From: "Mercedes Silver" [email protected] From Field. (these 4 fields can be in any order) To: <[email protected]> To Field. (these 4 fields can be in any order) Subject: Check out this scary thing! Subject Field. (these 4 fields can be in any order) Date: Sun, 27 Feb 2011 12:31:07 -0500 Date Field (d mm, yyyy HH:MM:SS zzz). (these 4 fields can be in any order) Message-ID: <8883802E7DC3402AA08372C008416D95@David> MIME-Version: 1.0 Content-Type: multipart/mixed; If a semicolon ends a line, the next line will be a continuation. boundary="----=_NextPart_000_0000_01CBD67A.365E6060" Notice we have a boundary declaration set within quotation marks. X-Priority: 3 (Normal) Obvious email extension as allowed by RFC 2045. Can be ignored. X-MSMail-Priority: Normal X-Mailer: Microsoft Outlook, Build 1234.5.6789 Importance: Normal ------=_NextPart_000_0000_01CBD67A.365E6060 Begin next block (note the 2 extra dashes at the beginning). Content-Type: multipart/alternative; boundary="----=_NextPart_001_0001_01CBD67A.365E8770" Notice we have ANOTHER boundary declaration, which is used here to wrap message types, both the main (plain), and alternate (HTML). ------=_NextPart_001_0001_01CBD67A.365E8770 Beginning of main body message area. Content-Type: text/plain; Main body is plain text charset="us-ascii" Content-Transfer-Encoding: 7bit Encoding, or rather, the lack thereof in the case of 7bit. Notice that a Content-Transfer-Encoding line is followed by a REQUIRED blank line. Picture image is attached. Beginning of plain text message. Mercedes ------=_NextPart_001_0001_01CBD67A.365E8770 End of plain text message, start of HTML alternate view. Content-Type: text/html; data is HTML. charset="us-ascii" Content-Transfer-Encoding: quoted-printable indicates text could have control conversions (though HTML usually covers most). The email is so short, there will not be any actual control code translation. <html> <body> Picture image is attached.</p> <p> <p> &nbsp; Mercedes<p> HTML forcing a leading space by inserting a non-breaking space. </body> </html> ------=_NextPart_001_0001_01CBD67A.365E8770-- End of HTML alternate view. ------=_NextPart_000_0000_01CBD67A.365E6060 Beginning of second boundary data. Start of binary image. Content-Type: image/jpeg; name="David Goben.JPG" filename for data. Content-Transfer-Encoding: base64 Content-Disposition: attachment; filename="David Goben.JPG" optional disposition field, which specifically indicates an attachment, plus a specific filename (some emails do not have a disposition, so depend on name, above) /9j/4AAQSkZJRgABAQEAYABgAAD/2wBDAAgGBgcGBQgHBwcJCQgKDBQNDAsLDBkSEw8UHRofHh0a HBwgJC4nICIsIxwcKDcpLDAxNDQ0Hyc5PTgyPC4zNDL/2wBDAQkJCQwLDBgNDRgyIRwhMjIyMjIy Binary image data. MjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjL/wAARCAD0AS4DASIA This blank area is ACTUALLY filed with about a page and a half of encoding... +4v9XWWZt7COWIKD7Dy6p/8ADPPhL/oI63/3/i/+N0UV3x2RxS3D/hnnwkP+Yjrf/f6L/wCN0f8A DPPhL/oI63/3/i/+N0UVRI5P2e/CKNk32ssPQzx4P5R11s/w+0mbw8dESe8htTH5W6JkD4xjqVI/ Siis6m6Kich/wzz4S/6COt/9/ov/AI3R/wAM8+Ev+gjrf/f+L/43RRWgjbtPhDoFnpI01LzU2gD7 8tLHuz/3xVm/+F2iaikazXWoAR9Nkif1SiivMlGPtL26np0JyUVZn//Z ------=_NextPart_000_0000_01CBD67A.365E6060-- End of second boundary data (note the 2 extra dashes at the beginning and end).

Page 41: Send (SMTP) and Retrieve (POP3) Email with Ease under VB.NET

–41–

Notice that an email can have multiple boundaries defined. I do not understand

why, because one would have served this email, but for some reason Microsoft Outlook chose to separate the main body and the alternate view from the

attachment(s), which are grouped by the outer blocking. This is also why some

simple email readers have trouble reading Outlook email files (and the fact that

they pack them full of self-defined extensions that are not specified by the RFC 2045 document, though

they may actually be useful and help define a future specification, which were last updated in 1996).

Further, notice that boundary definitions can be optionally enclosed within quotation marks, and that a

boundary will be unique to each email, being unlike any other line in all of its other text.

Another thing to notice is the Content-Disposition line. This simply identifies the data block as an

attachment. But we already know this from the previous Content-Type field. The only thing that RFC

2045 has to say about this field is that it should be ignored. So we will ignore it.

Issues that you may encounter regarding the Content-Type field will be in interpreting its fields. The first

part is simple enough, if you refer back to our tables, but notice that the first one is different if we have

attachments (attachments and/or alternate views). When the email has attachments of any sort, the first

parameter for Content-Type is supposed to be “multipart/alternative”, which indicates that we have

attachments or that we have alternate views. However, some email processors, such as Outlook, also can

specify “multipart/mixed” if they feature, say, alternate views and attachments. This is an allowed user-

defined extension. But we can still use it. We just need to check its left side for “multipart/”. If

“multipart/” leads, then we can be sure that the second parameter will specify a boundary definition

string (which Microsoft Outlook, but few others, embraces within quotation marks).

Because some email applications, such as Outlook, use multiple boundaries, we simply need to be sure

to absorb each one, and to test for each one, but we can treat any as though they were a single boundary,

simplifying our processing. That is, when any of the defined boundaries are encountered, then we will

simply assume we have hit a boundary and think nothing about it beyond that. However, as we check for

boundaries, we simply need to check each line for containing the boundary using an Instr() function,

because we really do not need to concern ourselves with the extra “--” leaders or trailers that we will

also find decorating a boundary within the body of the email.

If the first part of the Content-Type specifies a data type, such as “application/rtf” or “text/plain” or

“image/jpeg”, the second parameter will either be “name=”, or it will be a display format, such as

“charset=us-ascii”, or something similar. We will only concern ourselves when this second parameter

begins with “name=”, which will only happen when we are in an attachment, and the data can be saved

to a file, using the specified filename as a default, but only if the user chooses to save it. Otherwise, the

data is either the main message, or it is an alternate view.

Issues that you may encounter with regard to reading the Content-Transfer-Encoding field will be how

their parameter tags are actually embedded in the emails, as opposed to how you specified them. This is

because they cannot be declared in an enumeration the same way the RFC 2045 document dictates that

they must be provided. Consider the following table:

How We Declare the Content-Transfer-Encoding Value How They are Shipped/Received with Email QuotedPrintable quoted-printable SevenBit 7bit Base64 base64

When you read email, look to the data in the right column for what you will find. If it is not of these values,

we are instructed to assume the encoding is “base64”, regardless of what the Content-Transfer-Encoding

field specifies (of course, in your company, you may have a proprietary encryption format that you do not want competitors to read. In that case, you could specify that encryption specification here).

Page 42: Send (SMTP) and Retrieve (POP3) Email with Ease under VB.NET

–42–

Easily Extracting the Component Parts from an Email File Of course, a header contains many more fields than those shown previously, but the important headers

fields, to us at least, are the TO, FROM, DATE, and SUBJECT fields. To grab this important

information, the main message body, plus all attachments and alternate text, simply provide the Message

parameter of a POP3Message object to the GetEmailInfo() method (defined below).

The Retreive() method of POP3 takes a POP3Message object that has its MailID number set to the ID of

the mail that you want retrieved, which has previously been initiated by the POP3 class List() method, and

the ByteCount parameter is set to the length of the email, and its returns the POP3Message object that has its

Message parameter filled with an entire email; header, attachments, and all. It is the complete message as it

is stored on the server (though, as previously noted, any lone vbLf codes will be replaced by vbCrLf).

Provided the entire message data as a string, the GetEmailInfo() method will break that message data

up and distribute it primarily to within one, or several EmailItem class objects within an outer EmailInfo

class object, depending on the number of Alternate Views and Attachments included in the message.

Both the Alternate Views and the Attachments are maintained within Array Lists that allow it to store

multiples of each. You can check these ArrayList objects, named AlternateViews and Attachments

within the EmailInfo object, and if their Count parameter is non-zero, you can extract each EmailItem

object from their Items collection, and inspect them as you see fit.

There is one EmailItem object, named MessageBody, which is reserved for the main body of the email.

The Remainder of the EmailInfo object stores the e-mail’s FROM, TO, DATE, and SUBJECT properties. '******************************************************************************* ' Function Name : GetEmailInfo ' Purpose : Break email down into its component parts. ' : ' Returns : EmailInfo object with component parts of email broken down. ' : ' NOTES : This method uses classes EmailItems and EmailInfo. ' : The Message Body, and each AlternnateView or Attachment are ' : contained within EmailItem objects within the EmailIngo object. ' : ' : An EmailItem contains fields for FROM, TO, SUBJECT, Content-Type, ' : a flag indicating if the ContentTypeData is a filename or if it is ' : text formatting, content-transfer-encoding data, and the raw encoded, ' : data, whether it is a message or binary information. If the content- ' : transfer encoding is set to "base64", the data should be decoded ' : using the DecodeBase64() method. If it is "quoted-printable", the ' : data should be decoded using DecodeQuotedPrintable(). If it is ' : "7bit", it is 7-bit data and does not need to be decoded. '******************************************************************************* Public Shared Function GetEmailInfo(ByVal MailMessage As String) As EmailInfo Dim Info As New EmailInfo 'structure to hold breakdown of email Dim Ary() As String = Split(MailMessage, vbCrLf) 'break full email into lines Dim Idx As Integer = 0 'index into Ary() Dim MX As Integer = UBound(Ary) + 1 'find end if list+1 Dim Boundaries As New Collections.Generic.List(Of String) 'boundary definitions Dim IsMultiPart As Boolean = False 'true if we have multiple parts Dim SeekingEncoding As Boolean = False 'true if we are looking for encoding Dim BuildingDataBlock As Boolean = False 'true if we are building a data block Dim HaveMessageBody As Boolean = False 'true if we have the message body defined Dim ContentType As String = vbNullString 'hold last-defined Content Type Dim ContentTypeIsName As Boolean = False 'true of Content Type specified a file Dim ContentTypeData As String = vbNullString 'if block isan attachment Dim ContentEncoding As String = vbNullString 'hold last-defined Content Transfer Encoding Dim ContentBody As String = vbNullString 'block data accumulator '----------------------------------------------------------- Dim Inheader As Integer = 4 'flag for gathering To, From, Date, Subject Do Dim S As String = Ary(Idx) 'grab a line of data from the email ' check for important header items If CBool(Len(S)) AndAlso CBool(Inheader) Then 'if we are currently in the header... Dim I As Integer = InStr(S, ":") 'find field delimiter If CBool(I) Then 'found one? If VB.Right(S, 1) = ";" Then 'line continues? Idx += 1 'yes, so bump index S &= Ary(Idx).Trim(Chr(9), " "c) 'append next line next line End If Select Case LCase(VB.Left(S, I)) 'yes, check for one of 4 fields Case "from:" 'Found FROM field Info.FromData = Trim(Mid(S, I + 1)) 'stuff to structure Inheader -= 1 'drop 1 from flag Case "to:" 'Found TO field Info.ToData = Trim(Mid(S, I + 1)) 'stuff to structure Inheader -= 1 'drop 1 from flag Case "date:" 'Found DATE field Info.DateData = Trim(Mid(S, I + 1)) 'stuff to structure Inheader -= 1 'drop 1 from flag

Page 43: Send (SMTP) and Retrieve (POP3) Email with Ease under VB.NET

–43–

Case "subject:" 'Found SUBJECT field Info.SubjectData = Trim(Mid(S, I + 1)) 'stuff to structure Inheader -= 1 'drop 1 from flag End Select End If If Not CBool(Inheader) Then 'if InHeader flag is zero SeekingEncoding = True 'start looking for a Content-Transfer-Encoding field S = vbNullString 'purge current data End If End If '------------------------------------------------------- ' check for boundaries '------------------------------------------------------- If CBool(Len(S)) AndAlso CBool(Boundaries.Count) Then 'check any defined boundaries For Idy As Integer = 0 To Boundaries.Count - 1 If CBool(InStr(S, Boundaries.Item(Idy), CompareMethod.Text)) Then If BuildingDataBlock Then Dim Itm As New EmailItem 'create a new item Itm.ContentType = ContentType 'store content type Itm.ContentTypeData = ContentTypeData 'save filename or character set Itm.ContentTypeDataIsFilename = ContentTypeIsName 'save flag indicating if Attachment Itm.ContentEncoding = ContentEncoding 'store encoding Itm.ContentBody = ContentBody 'store data ContentBody = vbNullString 'reset accumulator If HaveMessageBody Then 'already have a message body? If ContentTypeIsName Then 'if an attachment Info.Attachments.Add(Itm) 'add an attachment Else 'otherwise an alternate view Info.AlternateViews.Add(Itm) End If Else Info.MessageBody = Itm 'else stuff new item to message body HaveMessageBody = True 'indicate we now have a message body End If ContentTypeData = vbNullString 'reset filename/charset BuildingDataBlock = False 'turn off building flag End If SeekingEncoding = True 'turn block seeing on again S = vbNullString 'purge current data Exit For End If Next End If '------------------------------------------------------- ' build data block '------------------------------------------------------- If BuildingDataBlock Then ContentBody &= S & vbCrLf 'add a line to content data End If '------------------------------------------------------- ' if seeking encoding '------------------------------------------------------- If CBool(Len(S)) AndAlso SeekingEncoding Then 'are we seeking TCE? Dim I As Integer = InStr(S, ":") 'yes, check for field delimiter If CBool(I) Then 'did we find one? Select Case LCase(VB.Left(S, I)) 'yes, check for types '======================================================= Case "content-type:" 'Content type? ContentType = Mid(S, I + 1).Trim(Chr(9), " "c) 'yes, so grab data If VB.Right(S, 1) = ";" Then 'more to add? Idx += 1 'yes, so bump index ContentType &= Ary(Idx).Trim(Chr(9), " "c) 'grab next line End If ContentTypeIsName = False 'init flag specifying a file as false Dim sbAry() As String = Split(ContentType, ";") 'now check the content type data ContentType = sbAry(0) 'keep first part for ContentType If StrComp(VB.Left(sbAry(0), 10), "multipart/", CompareMethod.Text) = 0 Then 'multipart, so grab second parameter (boundary definition), and strip any quotes Dim Bnd As String = Trim(Mid(sbAry(1), InStr(sbAry(1), "=") + 1)).Replace("""", vbNullString) Boundaries.Add(Bnd) 'and add a boundary ElseIf StrComp(VB.Left(sbAry(1), 5), "name=", CompareMethod.Text) = 0 Then ContentTypeIsName = True 'attachment if a filename specified (otherwise a view) sbAry = Split(sbAry(1), "=") 'multipart, so grab second parameter 'get second part of second parameter (filename definition) ContentTypeData = sbAry(1).Trim().Replace("""", vbNullString) 'strip any quotes Else ContentTypeData = sbAry(1) 'AlternateView, so stuff display character set If Len(Trim(Ary(Idx + 1))) = 0 Then 'if next line blank, assume ContentEncoding=base64 ContentEncoding = "base64" 'is blank, so assume base64 SeekingEncoding = False 'turn off seeking flag BuildingDataBlock = True 'turn on building data block flag Idx += 1 'bump to skip the blank line End If End If '=================================================== Case "content-transfer-encoding:" ContentEncoding = Mid(S, I + 1).Trim(Chr(9), " "c) 'yes, so grab data SeekingEncoding = False 'turn off seeking flag BuildingDataBlock = True 'turn on building data block flag Idx += 1 'bump to skip required following blank line End Select End If End If Idx += 1 'bump array index Loop While Idx < MX '----------------------------------------------------------- Return Info 'return with filled data block End Function '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Page 44: Send (SMTP) and Retrieve (POP3) Email with Ease under VB.NET

–44–

'+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ '******************************************************************************* ' Class Name : EmailItem (used by EmailInfo class) ' Purpose : Stores structure of an email block '******************************************************************************* Public Class EmailItem Public ContentType As String = vbNullString 'CONTENT-TYPE data Public ContentTypeData As String = vbNullString 'filename or text encoding Public ContentTypeDataIsFilename As Boolean = False 'True if ContentTypeData specifies a filename Public ContentEncoding As String = vbNullString 'CONTENT-TRANSFER-ENCODING data Public ContentBody As String = vbNullString 'raw data of block End Class '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ '******************************************************************************* ' Class Name : EmailInfo (used by GetEmailInfo method) ' Purpose : Store component parts of an Email '******************************************************************************* Public Class EmailInfo Public FromData As String = vbNullString 'FROM: Public ToData As String = vbNullString 'TO: Public DateData As String = vbNullString 'DATE: Public SubjectData As String = vbNullString 'SUBJECT: Public MessageBody As EmailItem 'contents of message body Public AlternateViews As New Collections.Generic.List(Of EmailItem) 'list of alternate views Public Attachments As New Collections.Generic.List(Of EmailItem) 'list of attachments End Class

Compiling Everything into a Class Library My email sending methods, BrainDeadSimpleEmailSend(),

QuickiEMail(), and SendEMail(), I embed in a class wrapper named

SMTP. To do this is really easy (see the full listing later, if you want to

just copy it).

I just select the menu options Project / Add Class…, and type SMTP

into the name field. Do not worry about it maintaining the default “.vb” file extension. It will be re-

added if you do not type it in. This creates the class shell:

Public Class SMTP End Class

I then decorate its heading with essential references and identification, being sure to include the imports

I had needed for the three methods, like so:

Option Strict On Option Explicit On '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ' SMTP - Copyright 2011, 2012 © by David Ross Goben. '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Imports System.Net, VB = Microsoft.VisualBasic '------------------------------------------------------------------------------- ' Class Name : SMTP ' Purpose : SMTP Interface Class '------------------------------------------------------------------------------- Public Class SMTP Class Body is here End Class

I finally copy my three methods, BrainDeadSimpleEmailSend(), QuickiEMail(), and SendEMail(),

within the SMTP class body. And that is all there is to creating the class. Easy, breezy, nice ’n’ easy!

But what this means, if we leave it at that, as an included class file, is that to invoke any of its methods

from this class, we must first instantiate an instance of this local SMTP class. For the SMTP class, I

would name an instance of it OutMail, and for the POP3 class, I would name an instance of it InMail.

For example, to send an email, I must now use code similar to the following:

'create a new instance of the SMTP class Dim OutMail As New SMTP 'build a message and send an email Dim S As String = "This is a test" & vbCrLf & "This is a second test" & vbCrLf OutMail.SendEMail("[email protected]", "[email protected]", "Test", S, False, "smtp.80micro.com") 'disengage the OutMail instance when we are done with it (at least put it within your Form_Closing() Event) OutMail = Nothing

Page 45: Send (SMTP) and Retrieve (POP3) Email with Ease under VB.NET

–45–

I will also take all my utilities and add them to a new class file named Utilities.vb. And like with the

SMTP class utilities, you will have to instantiate a Utilities class (this class will be covered, shortly).

UNLESS, we build a class library, reference the library from our application, and then import the class

library into our project file. This way, to access a method within the SMTP class, we can simply specify

SMP.methodname, or Utilities.methodname without having to specify either class. Of course, you will

also have to change all the Public methods defined within the SMP and Utilities classes to Public

Shared methods. However, if you do wish to still instantiate instances of SMP and Utilities classes to

local objects, simply do not declare them as Shared, much as they presently are. This is a personal

choice for you to make.

Either way, it is not quite as easy for our POP3 class. This is because the POP3 class inherits from

System.Net.Sockets.TcpClient, so we would still have to instantiate an instance of this class. But that is

no big deal, because so far, that is exactly what we have been doing. The only difference is, with the

class library we are about to build, that we would no longer be referencing a locally compiled class file.

But how would we reference this class library?

No problem. It is as easy as stepping on an upturned rake and rearranging your face with its handle.

But before we get to those simple steps, how about we actually build this class library, so we can

quickly prove that these steps are as simple as I claim them to be?

We have already defined our SMPT and POP3 class files (their complete listings are at the end of this

article). But we also want to create a Utilities class (after all, we have already been talking about the poor

thing as though it already exists).

We would create the Utilities class exactly the same way as we did the SMTP class. We would add a new

class and name it Utilities. We would then initially tattoo its new class shell like this:

Option Strict On Option Explicit On '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ' Utilities - Copyright 2011, 2012 © by David Ross Goben. '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Imports System.Text, VB = Microsoft.VisualBasic Public Class Utilities End Class

Next, we would add the following items to this Class file, all of which we have explored earlier, during the

course of this article/novella (it is also fully listed at the end of this article):

Method/Enum/Class Item Description

DecodeBase64ToStr() Decode Base64-encoded files to their original format and return a string. DecodeBase64ToBytes() Decode Base64-encoded files to their original format and return a Byte Array. DecodeQuotedPrintable() Method to clean typical control translations, or all of them. This should be invoked for all data coded Quoted-Printable.

DecodeHexDec() Decode Base16-encoded files to their original format, where each two characters prepresents a binary Byte. TextNeedsEncoding() Determine if HTML text, Rich Text, or Plain Text requires 8-bit code translation to 7-bit Quoted-Printable tags. Force7BitHtml() Method to convert 8-bit code in an HTML message to 7-bit. ForceQuotedPrintable() Method to force 8-bit code in a text message to 7-bit, without losing any data.

QConvertHTML2Text() Short-Form Convert HTML formatted text to plain text. ConvertHTML2Text() Convert HTML formatted text to plain text. Enum MediaTypes Enumeration used by GetMediaType. GetMediaType() Provide easy access to System.Net.Mime.MediaTypes text.

Enum TransferEncodings Structure used by GetTransferEncoding GetTransferEncoding() Provide easy access to System.Net.Mime.TransferEncoding data. GetEmailInfo() Break email down into its component parts. Class EmailItem (used by GetEmailInfo) Class EmailInfo (used by GetEmailInfo)

Once we have all three classed fully defined (and there are no errors laughing at us), we should further wrap

these three classes within a Class Library project named VBNetEmail, and build them to a DLL for easy

access by our application. This is incredibly easy to do.

Page 46: Send (SMTP) and Retrieve (POP3) Email with Ease under VB.NET

–46–

Building the VBNetMail Class Library To incorporate our three classes into a Class Library project named VBNetEmail is really easy. The first

thing to do is to save off and close your project that contains the classes SMTP, POP3, and Utilities, and remember where you saved them! Copy them from the listings at the end of this article, if you need to.

Now, create a new Class Library project named VBNetEmail.

Once created, in Project Explorer, you will see a Class File, Class1.vb. Simply right-click it and choose Delete.

We do not need it; we already have the class files we

require: SMTP.vb, POP3.vb, and Utilities.vb.

Next, select the menu options Project / Add Existing Item… Browse

to the location where you saved your POP3, SMTP, and Utilities class

files, select them, then click the Add button. The three class files

should then appear in your Project Explorer.

(We now have all we need to compile our class library.)

Select the menu options Build / Build VBNetEmail. We will end up

with a DLL file named VBNetEmail.dll (look within your Projects folder, Projects\VBNetEmail\VBNetEmail\obj\Release, for it). I tend

to test the class files within test projects, though later, once proven, I

copy them to a class library. I even do this for my related utilities,

adding them to a Utilities class, such as those used within the article.

Accessing your New VBNetEmail Class Library DLL from another Project

Once you have built the VBNetEmail.dll file, you can add it to other projects in just a few easy steps.

• In the Project Properties for your new application, make sure the References tab

is selected, and choose the ADD button.

• Select the Browse Tab, and then browse to your VBNetEmail Class Library

project (you should find yourself initially within your current project). Choose

the Up One Level button to back out of any current project; it is the folder icon with a curved green up arrow:

• Drill down into the VBNetEmail project to find the Obj folder, and open it.

• Within the Obj folder, select and open the Release folder.

• Within the Release folder, click the VBNetEmail.dll file and then select the OK

button (if you have one-click enabled, OK may be auto-selected).

• You will now find a reference to VBNetEmail listed in your References list. For

subsequent projects, you can simply choose the Recent Tab and very quickly select the VBNetEmail.dll file.

• Finally, in the project file that you will need to access the classes in, place the

line “Imports VBNetEmail” above the first class declaration at the top of the file.

You can now access your classes as though they were included in your project (well, in a way, they are).

Remember, the methods for the SMTP and Utilities classes can actually be accessed by simply specifying

SMTP or Utilities, hitting the dot, and selecting the method you want to use. To use the Inbound POP3

email class, you must instantiate an instance of the class, and then access its methods through that instance.

You are now ready for some serious email processing.

Page 47: Send (SMTP) and Retrieve (POP3) Email with Ease under VB.NET

–47–

The Complete SMTP.VB File Option Strict On Option Explicit On '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ' SMTP - Copyright 2011, 2012 © by David Ross Goben. '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Imports System.Net, VB = Microsoft.VisualBasic '------------------------------------------------------------------------------- ' Class Name : SMTP ' Purpose : SMTP Interface Class '------------------------------------------------------------------------------- Public Class SMTP '******************************************************************************* ' Function Name : BrainDeadSimpleEmailSend ' Purpose : Send super simple email message (works with most SMTP servers) '=============================================================================== 'NOTES: strFrom : Full email address of who is sending the email. ie, David Dingus <[email protected]> ' strTo : Full email address of who to send the email to. ie, "Bubba Dingus" <[email protected]> ' strSubject: Brief text regarding what the email concerns. ' strBody : text that comprises the message body of the email. ' smtpHost : This is the email host you are using for sending emails, such ' : as "smtp.comcast.net", "authsmtp.juno.com", etc. '******************************************************************************* Public Shared Sub BrainDeadSimpleEmailSend(ByVal strFrom As String, _ ByVal strTo As String, _ ByVal strSubject As String, _ ByVal strBody As String, _ ByVal smtpHost As String) Dim smtpEmail As New Mail.SmtpClient(smtpHost) 'create new SMTP client using TCP Port 25 smtpEmail.Send(strFrom, strTo, strSubject, strBody) 'send email End Sub '******************************************************************************* ' Function Name : QuickiEMail ' Purpose : Send a simple email message (but packed with a lot of muscle) '=============================================================================== 'NOTES: strFrom : Full email address of who is sending the email. ie, David Dingus <[email protected]> ' strTo : Full email address of who to send the email to. ie, "Bubba Dingus" <[email protected]> ' strSubject: Brief text regarding what the email concerns. ' strBody : text that comprises the message body of the email. ' smtpHost : This is the email host you are using for sending emails, such ' : as "smtp.gmail.com", "smtp.comcast.net", "authsmtp.juno.com", etc. ' smtpPort : TCP Communications Port to use. Most servers default to 25, though 465 (SSL) or 587 (TLS) are becoming popular. ' usesSLL : If this value is TRUE, then use SSL/TLS Authentication protocol for secure communications. ' SSLUsername: If usesSLL is True, this is the username to use for creating a credential. Leave blank if the same as strFrom. ' SSLPassword: If usesSLL is True, this is the password to use for creating a credential. If this field and SSLUsername ' : are blank, then default credentials will be used (only works on local, intranet servers). ' SSLDomain : If creating a credential when a specific domain is required, set this parameter, otherwise, leave it blank. '******************************************************************************* Public Shared Function QuickiEMail(ByVal strFrom As String, _ ByVal strTo As String, _ ByVal strSubject As String, _ ByVal strBody As String, _ ByVal smtpHost As String, _ Optional ByVal smtpPort As Integer = 25, _ Optional ByVal usesSSL As Boolean = False, _ Optional ByVal SSLUsername As String = vbNullString, _ Optional ByVal SSLPassword As String = vbNullString, _ Optional ByVal SSLDomain As String = vbNullString) As Boolean Try Dim smtpEmail As New Mail.SmtpClient(smtpHost, smtpPort) 'create new SMTP client smtpEmail.EnableSsl = usesSSL 'true if SSL Authentication required If usesSSL Then 'SSL authentication required? If Len(SSLUsername) = 0 AndAlso Len(SSLPassword) = 0 Then 'if both SSLUsername and SSLPassword are blank... smtpEmail.UseDefaultCredentials = True 'use default credentials Else 'otherwise, we must create a new credential If Not CBool(Len(SSLUsername)) Then 'if SSLUsername is blank, use strFrom smtpEmail.Credentials = New NetworkCredential(strFrom, SSLPassword, SSLDomain) Else smtpEmail.Credentials = New NetworkCredential(SSLUsername, SSLPassword, SSLDomain) End If End If End If smtpEmail.Send(strFrom, strTo, strSubject, strBody) 'send email using text/plain content type and QuotedPrintable encoding Catch e As Exception 'if error, report it MsgBox(e.Message, MsgBoxStyle.OkOnly Or MsgBoxStyle.Exclamation, "Mail Send Error") Return False 'return a failure flag End Try Return True 'if no error, then return a success flag End Function '******************************************************************************* ' Function Name : SendEMail ' Purpose : Send a more complex email message '=============================================================================== 'NOTES: strFrom : Full email address of who is sending the email. ie, David Dingus <[email protected]> ' strTo : Full email address of who to send the email to. ie, "Bubba Dingus" <[email protected]> ' : If multiple recipients, separate each full email address using a semicolon (;) ' strSubject: Brief text regarding what the email concerns. ' strBody : text that comprises the message body of the email. May be raw text or HTML code. ' IsHTML : True if the strBody data is HTML, or the type of data that would be contained within an HTML Body block. ' smtpHost : This is the email host you are using for sending emails, such ' : as "smtp.gmail.com", "smtp.comcast.net", "authsmtp.juno.com", etc. ' AltView : A System.Net.Mail.AlternateView object, such as Rich Text or HTML. ' : If need be, set AltView.ContentType.MediaType and AltView.TransferEncoding to properly format the AlternateView. ' : For example: AltView.ContentType.MediaType = Mime.MediaTypeNames.Text.Rtf ' : AltView.TransferEncoding = Mime.TransferEncoding.SevenBit ' StrCC : Send "carbon copies" of email to this or these recipients. ' : If multiple recipients, separate each full email address using a semicolon (;) ' strBcc : Blind Carbon Copy. Hide this or these recipients from view by others. ' : If multiple recipients, separate each full email address using a semicolon (;) ' strAttachments: A single filepath, or a list of filepaths to send to the recipient. ' : If multiple attachments, separate each filepath using a semicolon (;) (C:\my data\win32.txt; c:\jokes.rtf) ' : The contents of the attachments will be encoded and sent. ' : If you wish to send the attachment by specifying content type (MediaType) and content transfer encoding ' : (Encoding), then follow the attachment name with the MediaType and optional encoding (default is ' : application/octet-stream,Base64) by placing them within parentheses, and separated by a comma. For example: ' : C:\My Files\API32.txt (text/plain, SevenBit); C:\telnet.exe (application/octet-stream, Base64) ' : Where: The MediaType is determined from the System.Net.Mime.MediaTypeNames class, which ' : can specify Application, Image, or Text lists. For example, the above content type, ' : "text\plain", was defined by acquiring System.Net.Mime.MediaTypeNames.Text.Plain. ' : The second parameter, Encoding, is determined by the following the values specified by the ' : System.Net.Mime.TrasperEncoding enumeration: ' : QuotedPrintable (acquired by System.Net.Mime.TransferEncoding.QuotedPrintable.ToString) ' : Base64 (acquired by System.Net.Mime.TransferEncoding.Base64.ToString) ' : SevenBit (acquired by System.Net.Mime.TransferEncoding.SevenBit.ToString)

Page 48: Send (SMTP) and Retrieve (POP3) Email with Ease under VB.NET

–48–

' smtpPort : TCP Communications Port to use. Most servers default to 25. ' usesSLL : If this value is TRUE, then use SSL Authentication protocol for secure communications. ' SSLUsername: If usesSLL is True, this is the username to use for creating a credential. Leave blank if the same as strFrom. ' SSLPassword: If usesSLL is True, this is the password to use for creating a credential. If this field and SSLUsername ' : are blank, then default credentials will be used (only works on local, intranet servers). ' SSLDomain : If creating a credential when a specific domain is required, set this parameter, otherwise, leave it blank. '******************************************************************************* Public Shared Function SendEMail(ByVal strFrom As String, _ ByVal strTo As String, _ ByVal strSubject As String, _ ByVal strBody As String, _ ByVal IsHTML As Boolean, _ ByVal smtpHost As String, _ Optional ByVal AltView As Mail.AlternateView = Nothing, _ Optional ByVal strCC As String = vbNullString, _ Optional ByVal strBcc As String = vbNullString, _ Optional ByVal strAttachments As String = vbNullString, _ Optional ByVal smtpPort As Integer = 25, _ Optional ByVal usesSSL As Boolean = False, _ Optional ByVal SSLUsername As String = vbNullString, _ Optional ByVal SSLPassword As String = vbNullString, _ Optional ByVal SSLDomain As String = vbNullString) As Boolean Dim Email As New Mail.MailMessage 'create a new mail message With Email .From = New Mail.MailAddress(strFrom) 'add FROM to mail message (must be a Mail Address object) '------------------------------------------- Dim Ary() As String = Split(strTo, ";") 'add TO to mail message (possible list of email addresses; separated each with ";") For Idx As Integer = 0 To UBound(Ary) If Len(Trim(Ary(Idx))) <> 0 Then .To.Add(Trim(Ary(Idx))) 'add each TO recipent (primary recipients) Next '------------------------------------------- .Subject = strSubject 'add SUBJECT text line to mail message '------------------------------------------- .Body = strBody 'add BODY text of email to mail message. .IsBodyHtml = IsHTML 'indicate if the message body is actually HTML text. .IsBodyHtml = True '------------------------------------------- If AltView IsNot Nothing Then 'if an alternate view of plaint text message is defined... .AlternateViews.Add(AltView) 'add the alternate view End If '------------------------------------------- If CBool(Len(strCC)) Then 'add CC (Carbon Copy) email addresses to mail message Ary = Split(strCC, ";") '(possible list of email addresses, separated each with ";") For Idx As Integer = 0 To UBound(Ary) If Len(Trim(Ary(Idx))) <> 0 Then .CC.Add(Trim(Ary(Idx))) 'add each recipent Next End If '------------------------------------------- If CBool(Len(strBcc)) Then 'add Bcc (Blind Carbon Copy) email addresses to mail message Ary = Split(strBcc, ";") '(possible list of email addresses; separated each with ";") For Idx As Integer = 0 To UBound(Ary) If Len(Trim(Ary(Idx))) <> 0 Then .Bcc.Add(Trim(Ary(Idx))) 'add each recipent (hidden recipents) Next End If '------------------------------------------- If CBool(Len(strAttachments)) Then 'add any attachments to mail message Ary = Split(strAttachments, ";") '(possible list of file paths, separated each with ";") For Idx As Integer = 0 To UBound(Ary) 'process each attachment Dim attach As String = Trim(Ary(Idx)) 'get attachment data If Len(attach) <> 0 Then 'if an attachment present... Dim I As Integer = InStr(attach, "(") 'check for formatting instructions If CBool(I) Then 'formatting present? Dim Fmt As String 'yes, so set up format cache Fmt = Mid(attach, I + 1, Len(attach) - I - 1) 'get format data attach = Trim(VB.Left(attach, I - 1)) 'strip format data from the attachment path Dim Atch As New Mail.Attachment(attach) 'create a new attachment Dim fmts() As String = Split(Fmt, ",") 'break formatting up For I = 0 To UBound(fmts) 'process each format specification Fmt = Trim(fmts(I)) 'grab a format instruction If CBool(Len(Fmt)) Then 'data defined? Select Case I 'yes, so determine which type of instruction to process Case 0 'index 0 specified MediaType Atch.ContentType.MediaType = Fmt 'set media type to attachment Case 1 'index 1 specifes Encoding Select Case LCase(Fmt) 'check the encoding types and process accordingly Case "quotedprintable", "quoted-printable" Atch.TransferEncoding = Mime.TransferEncoding.QuotedPrintable Case "sevenbit", "7bit" Atch.TransferEncoding = Mime.TransferEncoding.SevenBit Case Else Atch.TransferEncoding = Mime.TransferEncoding.Base64 End Select End Select End If Next .Attachments.Add(Atch) 'add attachment to email Else .Attachments.Add(New Mail.Attachment(attach)) 'add filepath (if no format specified, encoded in effiecient Base64) End If End If Next End If End With '----------------------------------------------------------------------- 'now open the email server... Try Dim SmtpEmail As New Mail.SmtpClient(smtpHost, smtpPort) 'create new SMTP client on the SMTP server SmtpEmail.EnableSsl = usesSSL 'true if SSL Authentication required If usesSSL Then 'SSL authentication required? If Len(SSLUsername) = 0 AndAlso Len(SSLPassword) = 0 Then 'if both SSLUsername and SSLPassword are blank... SmtpEmail.UseDefaultCredentials = True 'use default credentials Else 'otherwise, we must create a new credential If Not CBool(Len(SSLUsername)) Then 'if SSLUsername is blank, use strFrom SmtpEmail.Credentials = New NetworkCredential(strFrom, SSLPassword, SSLDomain) Else SmtpEmail.Credentials = New NetworkCredential(SSLUsername, SSLPassword, SSLDomain) End If End If End If SmtpEmail.Send(Email) 'finally, send the email... Catch e As Exception 'if error, report it MsgBox(e.Message, MsgBoxStyle.OkOnly Or MsgBoxStyle.Exclamation, "Mail Error") Return False 'return failure flag End Try Return True 'return success flag End Function End Class

Page 49: Send (SMTP) and Retrieve (POP3) Email with Ease under VB.NET

–49–

The Complete POP3.VB File Option Strict On Option Explicit On '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ' POP3 - Copyright 2011, 2012 © by David Ross Goben. '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 'This VB.NET Code was inspired by C# code originally written Randy Charles Morin, 'author of KBCafe.com. ' ' I have optimized the heck out of the code to speed I/O and program execution, ' forcing a complete rewrite, I have added MANY language and POP3 enhancements, ' cleaned up a lot of clutter, and added Port and SSL support. ' Oh! And I include REAL comments. '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Imports System.Net, System.Text '------------------------------------------------------------------------------- ' Class Name : POP3 ' Purpose : POP3 Interface Class '------------------------------------------------------------------------------- Public Class POP3 Inherits Sockets.TcpClient 'this class shall inherit all the functionality of a TC/IP Client Dim Stream As Sockets.NetworkStream 'non-SSL stream object Dim UsesSSL As Boolean = False 'True if SLL authentication required Dim SslStream As Security.SslStream 'set to SSL stream supporting SSL authentication if UsesSSL is True Dim SslStreamDisposed As Boolean = False 'true if we disposed of SSL Stream object Public LastLineRead As String = vbNullString 'copy of the last response line read from the TCP server '******************************************************************************* ' Sub Name : Connect (This is the first the we do with a POP3 object) ' Purpose : Connect to the server using the Server, User Name, Password, ' : and a flag indicating if SSL authentication is required ' : ' Returns : Nothing ' : ' Typical TelNet I/O: 'telnet mail.domain.net 110 (submit) '+OK POP3 mail.domain.net v2011.83 server ready 'USER myusername (submit) '+OK User name accepted, password please 'PASS mysecretpassword (submit) '+OK Mailbox open, 3 messages (the server locks and opens the appropriate maildrop) '******************************************************************************* Public Overloads Sub Connect(ByVal Server As String, _ ByVal Username As String, _ ByVal Password As String, _ Optional ByVal InPort As Integer = 110, _ Optional ByVal UseSSL As Boolean = False) If Connected Then Disconnect() ' 'check underlying boolean flag to see if we are presently connected, 'and if so, disconnect that session UsesSSL = UseSSL 'set flag True or False for SSL authentication MyBase.Connect(Server, InPort) 'now connect to the server via our base class Stream = MyBase.GetStream 'before we can check for a response, we first have to set up a non-SSL stream If UsesSSL Then 'do we also need to use SSL authentication? SslStream = _ New Security.SslStream(Stream) 'yes, so build an SSL stream object on top of the non-SSL Network Stream SslStream.AuthenticateAsClient(Server) 'add authentication as a client to the server End If If Not CheckResponse() Then Exit Sub ' 'exit if an error was encountered If CBool(Len(Username)) Then 'if the username is defined (some servers will reject submissions) Me.Submit("USER " & Username & vbCrLf) 'submit user name If Not CheckResponse() Then Exit Sub ' 'exit if an error was encountered End If If CBool(Len(Password)) Then 'if the password is defined (some servers will reject submissions) Me.Submit("PASS " & Password & vbCrLf) 'submit password If Not CheckResponse() Then Exit Sub ' 'exit if an error was encountered End If End Sub '******************************************************************************* ' Function Name : CheckResponse ' Purpose : Check the response to a POP3 command ' : ' Returns : Boolean flag. True = Success, False = Failure ' : ' NOTE : All status responses from the server begin with: ' : +OK (OK; Success, or request granted) ' or : -ERR (NAGATIVE; error) '******************************************************************************* Public Function CheckResponse() As Boolean If Not IsConnected() Then Return False ' 'exit if not in TRANSACTION mode LastLineRead = Me.Response 'check response (and save response line) If (Left(LastLineRead, 3) <> "+OK") Then 'OK? Throw New POP3Exception(LastLineRead) 'no, so throw an exception Return False 'return failure flag End If Return True 'else return success flag End Function '******************************************************************************* ' Function Name : IsConnected ' Purpose : Return connected to Server state, throw error if not ' : ' Returns : Boolean Flag. True if connected to server ' : '******************************************************************************* Public Function IsConnected() As Boolean If Not Connected Then 'if not connected, throw an exception Throw New POP3Exception("Not Connected to an POP3 Server.") Return False 'return failure flag End If Return True 'Indicate that we are in the TRANSACTION state) End Function '******************************************************************************* ' Function Name : Response ' Purpose : get response from server (read from the mail stream into a buffer) ' : ' Returns : string of data from the server ' : ' NOTE : If a dataSize value > 1 is supplied, then those number of bytes will be streamed in. ' : Otherwise, the data will be read in a line at a time, and end with the line end code (Linefeed (vbLf) 10 decimal) '******************************************************************************* Public Function Response(Optional ByVal dataSize As Integer = 1) As String

Page 50: Send (SMTP) and Retrieve (POP3) Email with Ease under VB.NET

–50–

Dim enc As New ASCIIEncoding 'medium for ASCII representation of Unicode characters Dim ServerBufr() As Byte 'establish buffer Dim Index As Integer = 0 'init server buffer index and character counter If dataSize > 1 Then 'did invoker specify a data length to read? '------------------------------------------------------- ReDim ServerBufr(dataSize - 1) 'size to dataSize to read as a single stream block (allow for 0 index) Dim dtsz As Integer = dataSize Dim sz As Integer 'variable to store actual number of bytes read from the stream Do While Index < dataSize 'while we have not read the entire message... If UsesSSL Then 'process through SSL Stream if secure stream sz = SslStream.Read(ServerBufr, Index, dtsz) 'read a server-defined block of data from SSLstream Else 'else process through general TCP Stream sz = Stream.Read(ServerBufr, Index, dtsz) 'read a server-defined block of data from Network Stream End If If sz = 0 Then Return vbNullString ' 'we lost data, so we could not read the string Index += sz 'bump index for data count actually read dtsz -= sz 'drop amount left in buffer Loop Else '------------------------------------------------------ ReDim ServerBufr(255) 'initially dimension buffer to 256 bytes (including 0 offset) Do If UsesSSL Then 'process through SSL Stream if secure stream ServerBufr(Index) = CByte(SslStream.ReadByte) 'read a byte from SSLstream Else 'else process through general TCP Stream ServerBufr(Index) = CByte(Stream.ReadByte) 'read a byte from Network stream End If If ServerBufr(Index) = -1 Then Exit Do ' 'end of stream if -1 encountered Index += 1 'bump our offset index and counter If ServerBufr(Index - 1) = 10 Then Exit Do ' 'done with line if Newline code (10; Linefeed) read in If Index > UBound(ServerBufr) Then 'if the index points past end of buffer... ReDim Preserve ServerBufr(Index + 255) 'then bump buffer another 256 bytes (Inc Index), but keep existing data End If Loop 'loop until line read in End If Return enc.GetString(ServerBufr, 0, Index) 'decode from a byte array into a string and return the string End Function '******************************************************************************* ' Sub Name : Submit ' Purpose : Submit a request to the server ' : ' Returns : Nothing ' : ' NOTE : Command name must be in UPPERCASE, such as "PASS pw1Smorf". ' : "pass pw1Smorf" would not be acceptable, though some servers do allow for this, we should never assume it. '******************************************************************************* Public Sub Submit(ByVal message As String) Dim enc As New ASCIIEncoding 'medium for ASCII representation of Unicode characters Dim WriteBuffer() As Byte = enc.GetBytes(message) 'converts the submitted string into to a sequence of bytes If UsesSSL Then 'using SSL authentication? SslStream.Write(WriteBuffer, 0, WriteBuffer.Length) 'yes, so write SSL buffer using the SslStream object Else Stream.Write(WriteBuffer, 0, WriteBuffer.Length) 'else write to Network buffer using the non-SSL object End If End Sub '******************************************************************************* ' Sub Name : Disconnect (This is the last the we do with a POP3 object) ' Purpose : Disconnect from the server and have it enter the UPDATE mode ' : ' Returns : Nothing ' : ' Typical telNet I/O: 'QUIT (submit) '+OK Sayonara ' ' NOTE: When the client issues the QUIT command from the TRANSACTION state, ' the POP3 session enters the UPDATE state. (Note that if the client ' issues the QUIT command from the AUTHORIZATION state, the POP3 ' session terminates but does NOT enter the UPDATE state.) ' ' If a session terminates for some reason other than a client-issued ' QUIT command, the POP3 session does NOT enter the UPDATE state and ' MUST NOT remove any messages from the maildrop. ' ' The POP3 server removes all messages marked as deleted from the ' maildrop and replies as to the status of this operation. If there ' is an error, such as a resource shortage, encountered while removing ' messages, the maildrop may result in having some or none of the ' messages marked as deleted be removed. In no case may the server ' remove any messages not marked as deleted. ' ' Whether the removal was successful or not, the server then releases ' any exclusive-access lock on the maildrop and closes the TCP connection. '******************************************************************************* Public Sub Disconnect() Me.Submit("QUIT" & vbCrLf) 'submit quit request CheckResponse() 'check response If UsesSSL Then 'SSL authentication used? SslStream.Dispose() 'dispose of created SSL stream object if so SslStreamDisposed = True End If End Sub '******************************************************************************* ' Function Name : Statistics ' Purpose : Get the number of email messages and the total size as any integer array ' : ' Returns : 2-selement interger array. ' : Element(0) is the number of user email messages on the server ' : Element(1) is the total bytes of all messages taken up on the server ' : ' Typical telNet I/O: 'STAT (submit) '+OK 3 16487 (3 records (emails/messages) totaling 16487 bytes (octets)) '******************************************************************************* Public Function Statistics() As Integer() If Not IsConnected() Then Return Nothing ' 'exit if not in TRANSACTION mode Me.Submit("STAT" & vbCrLf) 'submit Statistics request LastLineRead = Me.Response 'check response If (Left(LastLineRead, 3) <> "+OK") Then 'OK? Throw New POP3Exception(LastLineRead) 'no, so throw an exception Return Nothing 'return failure flag End If Dim msgInfo() As String = Split(LastLineRead, " "c) 'separate by spaces, which divide its fields Dim Result(1) As Integer Result(0) = Integer.Parse(msgInfo(1)) 'get the number of emails Result(1) = Integer.Parse(msgInfo(2)) 'get the size of the email messages Return Result End Function

Page 51: Send (SMTP) and Retrieve (POP3) Email with Ease under VB.NET

–51–

'******************************************************************************* ' Function Name : List ' Purpose : Get the drop listing from the maildrop ' : ' Returns : Any Arraylist of POP3Message objects ' : ' Typical telNet I/O: 'LIST (submit) '+OK Mailbox scan listing follows '1 2532 (record index and size in bytes) '2 1610 '3 12345 '. (end of records terminator) '******************************************************************************* Public Function List() As ArrayList If Not IsConnected() Then Return Nothing ' 'exit if not in TRANSACTION mode Me.Submit("LIST" & vbCrLf) 'submit List request If Not CheckResponse() Then Return Nothing ' 'check for a response, but if an error, return nothing ' 'get a list of emails waiting on the server for the authenticated user ' Dim retval As New ArrayList 'set aside message list storage Do Dim response As String = Me.Response 'check response If (response = "." & vbCrLf) Then 'done with list? Exit Do 'yes End If Dim msg As New POP3Message 'establish a new message Dim msgInfo() As String = Split(response, " "c) 'separate by spaces, which divide its fields msg.MailID = Integer.Parse(msgInfo(0)) 'get the list item number msg.ByteCount = Integer.Parse(msgInfo(1)) 'get the size of the email message msg.Retrieved = False 'indicate its message body is not yet retreived retval.Add(msg) 'add a new entry into the retrieval list Loop Return retval 'return the list End Function '******************************************************************************* ' Function Name : GetHeader ' Purpose : Grab the email header and optionally a number of lines from the body ' : ' Returns : Gets the Email header of the selected email. If an integer value is ' : provided, that number of body lines will be returned. The returned ' : object is the submitted POP3Message. ' : ' Typical telNet I/O: 'TOP 1 0 (submit request for record 1's message header only, 0=no lines of body) '+OK Top of message follows ' xxxxx (header for current record is transmitted) '. (end of record terminator) ' 'TOP 1 10 (submit request for record 1's message header plus 10 lines of body data) '+OK Top of message follows ' xxxxx (header for current record is transmitted) ' xxxxx (first 10 lines of body) '. (end of record terminator) '******************************************************************************* Public Function GetHeader(ByRef msg As POP3Message, Optional ByVal BodyLines As Integer = 0) As POP3Message If Not IsConnected() Then Return Nothing ' 'exit if not in TRANSACTION mode Me.Submit("TOP " & msg.MailID.ToString & " " & BodyLines.ToString & vbCrLf) If Not CheckResponse() Then Return Nothing ''check for a response, but if an error, return nothing msg.Message = vbNullString 'erase current contents of the message, if any ' 'now process message data by binding the lines into a single string ' Do Dim response As String = Me.Response 'grab message line If response = "." & vbCrLf Then 'end of data? Exit Do 'yes, done with the loop if so End If msg.Message &= response 'else build message by appending the new line Loop Return msg 'return new filled Message object End Function '******************************************************************************* ' Function Name : Retrieve ' Purpose : Retrieve email from POP3 server for the provided POP3Message object ' : ' Returns : The submitted POP3 Message object with its Message property filled, ' : and its ByteCount property properly fitted to the message size. ' : ' NOTE : Some email servers are set up to automatically delete an email once ' : it is retrieved from the server. Outlook, Outlook Express, and ' : Windows Mail do this. It is an option under Juno and Gmail. So, if we ' : do not submit a POP3 QUIT (the Disconnect() method), but just close ' : out the POP3 object, the message(s) will not be deleted. ' : Even so, most Windows-based server-processors will add an additional ' : CR for each LF, but the reported email size does not account for them. ' : So we must retreive more data to account for this. ' ' Typical telNet I/O: 'RETR 1 (submit request to retrive record index 1 (cannot be an index marked for deletion)) '+OK 2532 octets (an octet is a fancy term for a 8-bit byte) ' xxxx (message header and message are retreived) '. (end of record terminator) '******************************************************************************* Public Function Retrieve(ByRef msg As POP3Message) As POP3Message If Not IsConnected() Then Return Nothing ' 'exit if not in TRANSACTION mode Me.Submit("RETR " & msg.MailID.ToString & vbCrLf) 'issue request for indicated message number If Not CheckResponse() Then Return Nothing ' 'check for a response, but if an error, return nothing msg.Message = Me.Response(msg.ByteCount) 'grab message line 'the stream reader automatically convers the NewLine code, vbLf, to vbCrLf, so the files is not yet 'fully read. For example, a files that was 233 lines will therefore have 233 more characters not 'yet read from the files when it has reached its reported data size. So we will scan these in. 'But even if this was not the case, the trailing "." & vbCrLf is still pending... Do Dim S As String = Response() 'grab more data If S = "." & vbCrLf Then 'end of data? Exit Do 'If so, then exit loop End If msg.Message &= S 'else tack data to end of message Loop 'keep trying msg.ByteCount = Len(msg.Message) 'ensure full size updated Return msg 'return new message object End Function '*******************************************************************************

Page 52: Send (SMTP) and Retrieve (POP3) Email with Ease under VB.NET

–52–

' Sub Name : Delete ' Purpose : Delete an email ' : ' Returns : Nothing ' : ' NOTE : Some email servers are set up to automatically delete an email once ' : it is retrieved from the server. Outlook, Outlook Express, and ' : Windows Mail do this. It is an option under Juno and Gmail. ' ' Typical telNet I/O: 'DELE 1 (submit request to delete record index 1) '+OK Message deleted '******************************************************************************* Public Sub Delete(ByVal msgHdr As POP3Message) If Not IsConnected() Then Exit Sub ' 'exit if not in TRANSACTION mode Me.Submit("DELE " & msgHdr.MailID.ToString & vbCrLf) 'submit Delete request CheckResponse() 'check response End Sub '******************************************************************************* ' Sub Name : Reset ' Purpose : Reset any deletion (automatic or manual) of all email from ' : the current session. ' : ' Returns : Nothing ' : ' Typical telNet I/O: 'RSET (submit) '+OK Reset state '******************************************************************************* Public Sub Reset() If Not IsConnected() Then Exit Sub ''exit if not in TRANSACTION mode Me.Submit("RSET" & vbCrLf) 'submit Reset request CheckResponse() 'check response End Sub '******************************************************************************* ' Function Name : NOOP (No Operation) ' Purpose : Does nothing. Juts gets a position response from the server ' : ' Returns : Boolean flag. False if disconnected, else True if connected. ' : ' NOTE : This NO OPERATION command is useful when you have a server that ' : automatically disconnects after a certain idle period of activity. ' : This command can be issued by a timer that also monitors users ' : inactivity, and issues a NOOP to reset the server timer. ' : ' Typical telNet I/O: 'NOOP (submit) '+OK '******************************************************************************* Public Function NOOP() As Boolean If Not IsConnected() Then Return False 'exit if not in TRANSACTION mode Me.Submit("NOOP") Return CheckResponse() End Function '******************************************************************************* ' Function Name : Finalize ' Purpose : remove SSL Stream object if not removed '******************************************************************************* Protected Overrides Sub Finalize() If SslStream IsNot Nothing AndAlso Not SslStreamDisposed Then 'SSL Stream object Disposed? SslStream.Dispose() 'no, so do it End If MyBase.Finalize() 'then do normal finalization End Sub End Class '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ '------------------------------------------------------------------------------- ' Class Name : POP3Message ' Purpose : POP3 message data '------------------------------------------------------------------------------- Public Class POP3Message Public MailID As Integer = 0 'message number Public ByteCount As Integer = 0 'length of message in bytes Public Retrieved As Boolean = False 'flag indicating if the message has been retrieved Public Message As String = vbNullString 'the text of the message Public Overrides Function ToString() As String Return Message End Function End Class '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ '------------------------------------------------------------------------------- ' Class Name : POP3Exception ' Purpose : process exception ' NOTE : This is a normal exception, but we wrap it to give it an identy ' : that can be associated with our POP3 class '------------------------------------------------------------------------------- Public Class POP3Exception Inherits ApplicationException Public Sub New(ByVal str As String) MyBase.New(str) End Sub End Class

Page 53: Send (SMTP) and Retrieve (POP3) Email with Ease under VB.NET

–53–

The Complete Utilities.VB File Option Strict On Option Explicit On '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ' Utilities - Copyright 2011, 2012 © by David Ross Goben. '~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Imports System.Text, VB = Microsoft.VisualBasic Public Class Utilities '******************************************************************************* ' Function Name : DecodeBase64ToStr ' Purpose : Decode a provided raw email message string that is encoded to Base64. ' : ' Returns : Decoded String ' : ' NOTES : note that the lone vbCrLf at the end of lines is filtered out. '******************************************************************************* Public Shared Function DecodeBase64ToStr(ByVal strData As String) As String Return Encoding.UTF8.GetChars(DecodeBase64ToBytes(strData)) End Function '******************************************************************************* ' Function Name : DecodeBase64ToBytes ' Purpose : Decode a provided raw email message string that is encoded to Base64. ' : ' Returns : Decoded binary Byte Array ' : ' NOTES : note that the lone vbCrLf at the end of lines is filtered out. '******************************************************************************* 'this modification returns a Byte Array of the Base64 encoded source data Public Shared Function DecodeBase64ToBytes(ByVal strData As String) As Byte() Return System.Convert.FromBase64String(strData.Replace(vbCrLf, vbNullString)) End Function '========================================================================== '========================================================================== '******************************************************************************* ' Function Name : DecodeQuotedPrintable ' Purpose : Method to clean typical control translations, or all of them. ' : This should be invoked for all data coded Quoted-Printable. ' : ' Returns : Provided a raw message string block, it returns a decoded string. ' : ' NOTES : Typical cleaning involves changing "=0D" to vbCr, "=0A" to vbLf, ' : "=20" to a space, and "=3D" to "=", plus any line wrap ' : terminators at the end of lines to vbNullstring. ' : ' : A StringBuilder object will be used, which will very quickly ' : do a replacement of all control code translations using fewer ' : resources, and what resources that are used will be instantly ' : flushed when the method exits. '******************************************************************************* Public Shared Function DecodeQuotedPrintable(ByVal Message As String, Optional ByVal QuickClean As Boolean = False) As String 'set up StringBuilder object with data stripped of any line continuation tags Dim Msg As New StringBuilder(Message.Replace("=" & vbCrLf, vbNullString)) If QuickClean Then 'perform a quick clean (clean up common basics) Return Msg.Replace("=" & vbCrLf, vbNullString).Replace("=0D", vbCr).Replace("=0A", _ vbLf).Replace("=20", " ").Replace("=3D", "=").ToString Else 'perform total cleaning 'store 2-character hex values that require a leading "0" Dim HxData As String = "X0102030405060708090A0B0C0D0E0F" For Idx As Integer = 1 To &HF 'initially process codes 1-15, which require a leading zero Msg.Replace("=" & Mid(HxData, Idx << 1, 2), Chr(Idx)) 'replace hex data with single character code (SHIFT is faster) Next For idx As Integer = &H10 To &HFF 'process the whole 8-bit extended ASCII gambit Msg.Replace("=" & Hex(idx), Chr(idx)) 'replace hex data with single character code Next Return Msg.ToString 'return result string End If End Function '========================================================================== '========================================================================== '******************************************************************************* ' Function Name : DecodeBinHex ' Purpose : Decode a provided raw email message string that is encoded to BinHex. ' : ' Returns : Decoded String ' : ' NOTES : note that the lone vbCrLf at the end of lines is filtered out. '******************************************************************************* Public Shared Function DecodeBinHex(ByVal StrData As String) As Byte() Dim Src() As Byte = Encoding.UTF8.GetBytes(StrData.Replace(vbCrLf, vbNullString).ToUpper) Dim Result() As Byte 'init output buffer ReDim Result(UBound(Src) \ 2) 'set initial dimension to 1024 bytes (includes offset 0) Dim Index As Integer = 0 'init index for Result() array For Idx As Integer = 0 To UBound(Src) Step 2 'scan the string, 2 hex characters at a time Dim CL As Integer = Src(Idx) - 48 'Convert "0" - "F" to 0-F If CL > 10 Then CL -= 7 Dim CR As Integer = Src(Idx + 1) - 48 'do the same for the right hex digit If CR > 10 Then CR -= 7 If Index > UBound(Result) Then ReDim Preserve Result(Index + 255) 'bump by 256 (allow for Index offset) End If Result(Index) = CByte(CL * 16 + CR) 'stuff byte value Index += 1 'bump index Next ReDim Preserve Result(Index - 1) 'set array to final size Return Result 'return the final result End Function '========================================================================== '==========================================================================

Page 54: Send (SMTP) and Retrieve (POP3) Email with Ease under VB.NET

–54–

'******************************************************************************* ' Function Name : TextNeedsEncoding ' Purpose : Determine if HTML text, Rich Text, or Plain Text requires ' : 8-bit code translation to 7-bit Quoted-Printable tags. ' : ' Returns : Provided a source string, it returns a boolena flag. ' : If the returned value is true, the source contains 8-bit data ' : and will be encoded by server. ' : ' NOTES : If text data contains 8-bit values, the default .NET ' : SMTP processor will force this code to be encoded to Base64, ' : even if only a single byte is 8-bit. ' : ' : To avoid this, the Force7BitHtml() method can be invoked on ' : HTML text to ensure that it is 7-bit encoded so that it can ' : be processed as Quoted-Printable or as 7Bit. The ForceQuotedPrintable() ' : method performs essential conversions for non-HTML text, but this ' : would be best served in Attachments and Alternate Views. '******************************************************************************* Public Shared Function TextNeedsEncoding(ByVal Message As String) As Boolean Dim Byt() As Byte = Encoding.UTF8.GetBytes(Message) 'convert message to byte array For Each B As Byte In Byt If CBool(B And &H80) Then Return True Next Return False End Function '========================================================================== '========================================================================== '******************************************************************************* ' Function Name : Force7BitHtml ' Purpose : Method to convert 8-bit code in an HTML message to 7-bit. ' : ' Returns : Provided a string containing HTML code, it will return a string ' : containing HTML code that does not have any 8-bit data embedded. ' : ' NOTES : If any characters in an HTML text string are 8-bit (values ' : greater than 127), then they are converted into a special ' : 7-bit HTML Entity Number, For Example, code 149 (•) is an 8-bit ' : value that can be changed to HTML "&#149;", which will ensure ' : that it will still be displayed on the HTML page, but the HTML ' : souce code will no longer carry an actual 8-bit value. If such ' : code had not been corrected, the encoding of the data would be ' : forced to change from quoted-printable to Base64, because that ' : would be the only way the email processor could guarantee that ' : the email text was fully intact. '******************************************************************************* Public Shared Function Force7BitHtml(ByVal HtmlSource As String) As String Dim Sb As New StringBuilder 'set up string builder for appending data For Idx As Integer = 1 To Len(HtmlSource) Dim C As Integer = AscW(Mid(HtmlSource, Idx, 1)) 'get a single character from the source Select Case C 'check each character Case Is > &H7F, Is < 0 'if 8-bit or unicode code Sb.Append("&#" & C.ToString & ";") 'convert to 7-bit HTML ecoder Case Else Sb.Append(ChrW(C)) 'else save text regardless End Select Next Return Sb.ToString End Function '========================================================================== '========================================================================== '******************************************************************************* ' Function Name : ForceQuotedPrintable ' Purpose : Force 8-bit code in a text message to 7-bit, without data loss. ' : ' Returns : Provided a source string that contains 8-bit data, the 8-bit ' : data is converted to Hex-Tags, and the returned string is 7-bit. ' : ' NOTES : if any characters in a text string are 8-bit (values greater ' : than 127), then they are converted into special 7-bit tags. ' : For Example, code 149 (•) is an 8-bit value that can be changed ' : to hex "=95", which will ensure that it will still be displayed ' : in the text, but the text data will no longer carry an actual ' : of the data would be forced to change from quoted-printable or 7bit ' : to Base64, because that would be the only way the email processor ' : Base64, because that would be the only way the email processor ' : could guarantee that the email text was fully intact. However, ' : you will have to use the DecodeQuotedPrintable() method to convert ' : it back to its original text form. ' : ' : The Encoded text will begin with "=00". Because unencoded null codes ' : are not permitted in email data, you can use this to instantly ' : determine on the receiving end that this code will need to be ' : processed by DecodeQuotedPrintable() a second time (if initially ' : encoded as Quoted-Printable). A second pass would be required, ' : because if this translated code was afterward encoded as Quoted- ' : Printable, and all the "=xx" byte-translations, would be ' : reinterpreted as "=3Dxx", which DecodeQuotedPrintable() would ' : convert back to "=xx", so passing through a second time would ' : properly convert the additional encoding. Further, by checking the ' : text startiing with "=00", you would know that you would need to ' : double-decode the text. Also, you would want to initially skip this ' : initial tag when passing it the second time to DecodeQuotedPrintable(): ' : ' : Dim Result As String = DecodeQuotedPrintable(Message) 'initially decode Quoted-Printable text ' : If VB.Left(Result, 3) = "=00" Then 'tagged as pre-encoded? ' : Return DecodeQuotedPrintable(Mid(Result, 4)) 'yes, so decode again and return, less initial null byte ' : Else ' : Return Result 'otherwise, return result of decoding ' : End If '******************************************************************************* Public Shared Function ForceQuotedPrintable(ByVal Message As String) As String Dim Byt() As Byte = Encoding.UTF8.GetBytes(Message) 'convert message to byte array Dim Sb As New StringBuilder("=00") 'set up string builder for appending data For Each B As Byte In Byt Select Case B 'check each byte Case Is > &H7F 'if 8-bit code Sb.Append("=" & Hex(B)) 'convert to 7-bit tag Case Else Sb.Append(Chr(B)) 'else save text regardless End Select Next Return Sb.ToString End Function '==========================================================================

Page 55: Send (SMTP) and Retrieve (POP3) Email with Ease under VB.NET

–55–

'========================================================================== '******************************************************************************* ' Function Name : QConvertHTML2Text ' Purpose : Short-Form Convert HTML formatted text to plain text ' : ' Returns : Provided a simple HTML source string, it will return a Plain Text ' : string with HTML code removed. '******************************************************************************* Public Shared Function QConvertHTML2Text(ByVal HTMLText As String) As String Return RegularExpressions.Regex.Replace(HTMLText.Replace("&nbsp;", " ").Replace("&quot;", """").Replace("&apos;", _ "'"), "<[^>]*>", "").Replace("&lt;", "<").Replace("&gt;", ">").Replace("&amp;", "&").Replace(";;", ";") End Function '========================================================================== '========================================================================== '******************************************************************************* ' Function Name : ConvertHTML2Text ' Purpose : Convert HTML formatted text to plain text ' : ' Returns : Provided a complex HTML string, it will return a Plain Text string ' : with all HTML codes and formatting removed from it. ' : ' NOTE : Numerous of these conversions will convert the text to 8-bit, ' : though most of these sysmbols will not be encountered in most ' : HTML documents we produce. But regardless of that, if you wish ' : to make this conversion the main body message of an email, you ' : may have to further convert this using ForceQuotedPrintable() ' : to maintain Quoted-Printable encoding and avoid Base64, even ' : though this is typically not an issue. However, some few really ' : primitive email readers, typically those that simply allow you ' : to preview email messages, without fully loading them, will not ' ' know how to support Base64, or will not bother with it, but simply ' : display the raw data. RFC 2045 requires email handlers to support it. '******************************************************************************* Public Shared Function ConvertHTML2Text(ByVal HTMLText As String) As String 'instantiate an initially blank StringBuilder object Dim Sb As New StringBuilder() 'first remove leading whitespace of each line and append the result to the StringBuilder Dim ary() As String = Split(HTMLText, vbCrLf) For Each S As String In ary Sb.Append(S.TrimStart(Chr(9), " "c)) Next 'replace reserved entities (except <, >, and &) Sb.Replace("&quot;", """").Replace("&apos;", "'").Replace("&nbsp;", " ") 'replace HTML paragraph, line breaks, and table entry terminators with vbCrLf Sb.Replace("<p>", vbCrLf).Replace("<P>", vbCrLf).Replace("</p>", vbCrLf).Replace("</P>", vbCrLf).Replace("<br>", _ vbCrLf).Replace("<BR>", vbCrLf).Replace("</td>", vbCrLf).Replace("</TD>", vbCrLf) 'replace ISO 8859-1 Symbols (160-255). Note that any matches will make the text 8-bit Sb.Replace("&iexcl;", "¡").Replace("&cent;", "¢").Replace("&pound;", "£").Replace("&curren;", _ "¤").Replace("&yen;", "¥").Replace("&brvbar;", "¦").Replace("&sect;", "§").Replace("&uml;", _ "¨").Replace("&copy;", "©").Replace("&ordf;", "ª").Replace("&laquo;", "«").Replace("&not;", _ "¬").Replace("&shy;", "-").Replace("&reg;", "®").Replace("&macr;", "¯").Replace("&deg;", _ "°").Replace("&plusmn;", "±").Replace("&sup2;", "²").Replace("&sup3;", "³").Replace("&acute;", _ "´").Replace("&micro;", "µ").Replace("&para;", "¶").Replace("&middot;", "•").Replace("&cedil;", _ "¸").Replace("&sup1;", "¹").Replace("&ordm;", "º").Replace("&raquo;", "»").Replace("&frac14;", _ "¼").Replace("&frac12;", "½").Replace("&frac34;", "¾").Replace("&iquest;", "¿").Replace("&times;", _ "×").Replace("&divide;", "÷") 'replace ISO 8859-1 characters. Note that any matches will make the text 8-bit Sb.Replace("&Agrave;", "À").Replace("&Aacute;", "Á").Replace("&Acirc;", "Â").Replace("&Atilde;", "Ã").Replace("&Auml;", _ "Ä").Replace("&Aring;", "Å").Replace("&AElig;", "Æ").Replace("&Ccedil;", "Ç").Replace("&Egrave;", _ "È").Replace("&Eacute;", "É").Replace("&Ecirc;", "Ê").Replace("&Euml;", "Ë").Replace("&Igrave;", _ "Ì").Replace("&Iacute;", "Í").Replace("&Icirc;", "Î").Replace("&Iuml;", "Ï").Replace("&ETH;", _ "Ð").Replace("&Ntilde;", "Ñ").Replace("&Ograve;", "Ò").Replace("&Oacute;", "Ó").Replace("&Ocirc;", _ "Ô").Replace("&Otilde;", "Õ").Replace("&Ouml;", "Ö").Replace("&Oslash;", "Ø").Replace("&Ugrave;", _ "Ù").Replace("&Uacute;", "Ú").Replace("&Ucirc;", "Û").Replace("&Uuml;", "Ü").Replace("&Yacute;", _ "Ý").Replace("&THORN;", "Þ").Replace("&szlig;", "ß").Replace("&agrave;", "à").Replace("&aacute;", _ "á").Replace("&acirc;", "â").Replace("&atilde;", "ã").Replace("&auml;", "ä").Replace("&aring;", _ "å").Replace("&aelig;", "æ").Replace("&ccedil;", "ç").Replace("&egrave;", "è").Replace("&eacute;", _ "é").Replace("&ecirc;", "ê").Replace("&euml;", "ë").Replace("&igrave;", "ì").Replace("&iacute;", _ "í").Replace("&icirc;", "î").Replace("&iuml;", "ï").Replace("&eth;", "ð").Replace("&ntilde;", _ "ñ").Replace("&ograve;", "ò").Replace("&oacute;", "ó").Replace("&ocirc;", "ô").Replace("&otilde;", _ "õ").Replace("&ouml;", "ö").Replace("&oslash;", "ø").Replace("&ugrave;", "ù").Replace("&uacute;", _ "ú").Replace("&ucirc;", "û").Replace("&uuml;", "ü").Replace("&yacute;", "ý").Replace("&thorn;", _ "þ").Replace("&yuml;", "ÿ") 'replace Math Symbols Supported by HTML. Note that any matches will make the text 8-bit Sb.Replace("&forall;", "∀∀∀∀").Replace("&part;", "∂").Replace("&exist;", "∃∃∃∃").Replace("&empty;", "∅∅∅∅").Replace("&nabla;", _ "∇∇∇∇").Replace("&isin;", "∈∈∈∈").Replace("&notin;", "∉∉∉∉").Replace("&ni;", "∋∋∋∋").Replace("&prod;", _ "∏").Replace("&sum;", "∑").Replace("&minus;", "−").Replace("&lowast;", "∗∗∗∗").Replace("&radic;", _ "√").Replace("&prop;", "∝∝∝∝").Replace("&infin;", "∞").Replace("&ang;", "∠∠∠∠").Replace("&and;", _ "∧∧∧∧").Replace("&or;", "∨∨∨∨").Replace("&cap;", "∩").Replace("&cup;", "∪∪∪∪").Replace("&int;", _ "∫").Replace("&there4;", "∴∴∴∴").Replace("&sim;", "∼∼∼∼").Replace("&cong;", "≅≅≅≅").Replace("&asymp;", _ "≈").Replace("&ne;", "≠").Replace("&equiv;", "≡").Replace("&le;", "≤").Replace("&ge;", _ "≥").Replace("&sub;", "⊂⊂⊂⊂").Replace("&sup;", "⊃⊃⊃⊃").Replace("&nsub;", "⊄⊄⊄⊄").Replace("&sube;", _ "⊆⊆⊆⊆").Replace("&supe;", "⊇⊇⊇⊇").Replace("&oplus;", "⊕⊕⊕⊕").Replace("&otimes;", "⊗⊗⊗⊗").Replace("&perp;", _ "⊥⊥⊥⊥").Replace("&sdot;", "⋅⋅⋅⋅") 'replace Greek Letters Supported by HTML. Note that any matches will make the text 8-bit Sb.Replace("&Alpha;", "Α").Replace("&Beta;", "Β").Replace("&Gamma;", "Γ").Replace("&Delta;", "∆").Replace("&Epsilon;", _ "Ε").Replace("&Zeta;", "Ζ").Replace("&Eta;", "Η").Replace("&Theta;", "Θ").Replace("&Iota;", _ "Ι").Replace("&Kappa;", "Κ").Replace("&Lambda;", "Λ").Replace("&Mu;", "Μ").Replace("&Nu;", _ "Ν").Replace("&Xi;", "Ξ").Replace("&Omicron;", "Ο").Replace("&Pi;", "Π").Replace("&Rho;", _ "Ρ").Replace("&Sigma;", "Σ").Replace("&Tau;", "Τ").Replace("&Upsilon;", "Υ").Replace("&Phi;", _ "Φ").Replace("&Chi;", "Χ").Replace("&Psi;", "Ψ").Replace("&Omega;", "Ω").Replace("&alpha;", _ "α").Replace("&beta;", "β").Replace("&gamma;", "γ").Replace("&delta;", "δ").Replace("&epsilon;", _ "ε").Replace("&zeta;", "ζ").Replace("&eta;", "η").Replace("&theta;", "θ").Replace("&iota;", _ "ι").Replace("&kappa;", "κ").Replace("&lambda;", "λ").Replace("&mu;", "µ").Replace("&nu;", _ "ν").Replace("&xi;", "ξ").Replace("&omicron;", "ο").Replace("&pi;", "π").Replace("&rho;", _ "ρ").Replace("&sigmaf;", "ς").Replace("&sigma;", "σ").Replace("&tau;", "τ").Replace("&upsilon;", _ "υ").Replace("&phi;", "φ").Replace("&chi;", "χ").Replace("&psi;", "ψ").Replace("&omega;", _ "ω").Replace("&thetasym;", "ϑ").Replace("&upsih;", "ϒ").Replace("&piv;", "ϖ") 'replace Other Entities Supported by HTML. Note that any matches will make the text 8-bit Sb.Replace("&OElig;", "Œ").Replace("&oelig;", "œ").Replace("&Scaron;", "Š").Replace("&scaron;", "š").Replace("&Yuml;", _ "Ÿ").Replace("&fnof;", "ƒ").Replace("&circ;", "ˆ").Replace("&tilde;", "˜").Replace("&ensp;", _ " ").Replace("&emsp;", " ").Replace("&thinsp;", "    ").Replace("&ndash;", "–").Replace("&mdash;", _ "—").Replace("&lsquo;", "‘").Replace("&rsquo;", "’").Replace("&sbquo;", "‚").Replace("&ldquo;", _ " ").Replace("&rdquo;", " ").Replace("&bdquo;", "„").Replace("&dagger;", "†").Replace("&Dagger;", _ "‡").Replace("&bull;", "•").Replace("&hellip;", "…").Replace("&permil;", "‰").Replace("&prime;", _ "′").Replace("&Prime;", "″").Replace("&lsaquo;", "‹").Replace("&rsaquo;", "›").Replace("&oline;", _ "").Replace("&euro;", "€").Replace("&trade;", "™").Replace("&larr;", "←").Replace("&uarr;", _

Page 56: Send (SMTP) and Retrieve (POP3) Email with Ease under VB.NET

–56–

"↑").Replace("&rarr;", "→").Replace("&darr;", "↓").Replace("&harr;", "↔").Replace("&crarr;", _

"↵↵↵↵").Replace("&lceil;", "⌈⌈⌈⌈").Replace("&rceil;", "⌉⌉⌉⌉").Replace("&lfloor;", "⌊⌊⌊⌊").Replace("&rfloor;", _ "⌋⌋⌋⌋").Replace("&loz;", "◊").Replace("&spades;", "♠").Replace("&clubs;", "♣").Replace("&hearts;", _ "♥").Replace("&diams;", "♦") 'replace special ASCII coding entities that were not captured by the above. Note that values > 127 will make the text 8-bit For Idx As Integer = 1 To 255 'See www.w3schools.com/tags/ref_entities.asp Sb.Replace("&#" & Idx.ToString & ";", Chr(Idx)) 'replace most common numeric entities Next 'Ensure header definitions are followed by vbCrLf Dim NewText As String = RegularExpressions.Regex.Replace(Sb.ToString(), "</H[^>]*>", vbCrLf) 'Also seek out other Unicode encoded number entities not covered by the above and individually update them Dim Idy As Integer = InStr(NewText, "&#") 'check for a numeric entity Do While Idy <> 0 'loop as long as we find one Dim Idz As Integer = InStr(Idy, NewText, ";") 'find terminating semicolon Dim S As String = Mid(NewText, Idy, Idz - Idy + 1) 'grab expression RegularExpressions.Regex.Replace(NewText, S, Chr(CInt(Mid(S, 3, Len(S) - 3)))) 'replace expression InStr(Idy + 1, NewText, "&#") Loop 'strip remaining HTML text tags, replace < and > placeholders, convert ampersand, replace ;; with ;, then return result Return RegularExpressions.Regex.Replace(NewText, "<[^>]*>", "").Replace("&lt;", _ "<").Replace("&gt;", ">").Replace("&amp;", "&").Replace(";;", ";") End Function '========================================================================== '========================================================================== '******************************************************************************* ' Enum MediaTypes: Structure used by GetMediaType '******************************************************************************* Public Enum MediaTypes As Integer ApplicationOctet ' 0 = Integer Value ApplicationPdf ' 1 ApplicationRtf ' 2 ApplicationSoap ' 3 ApplicationZip ' 4 ImageGif ' 5 ImageJpeg ' 6 ImageTiff ' 7 TextHtml ' 8 TextPlain ' 9 TextRich '10 TextXml '11 End Enum '******************************************************************************* ' Function Name : GetMediaType ' Purpose : Provide easy access to System.Net.Mime.MediaTypes text ' : ' Returns : provided a MediaTypes enumeration value, a string representing ' : the selected type will be returned. '******************************************************************************* Public Shared Function GetMediaType(ByVal MediaType As MediaTypes) As String Select Case MediaType Case MediaTypes.ApplicationPdf Return "application/pdf" Case MediaTypes.ApplicationRtf Return "application/rtf" Case MediaTypes.ApplicationSoap Return "application/soap+xml" Case MediaTypes.ApplicationZip Return "application/zip" Case MediaTypes.ImageGif Return "image/gif" Case MediaTypes.ImageJpeg Return "image/jpeg" Case MediaTypes.ImageTiff Return "image/tiff" Case MediaTypes.TextHtml Return "text/html" Case MediaTypes.TextPlain Return "text/plain" Case MediaTypes.TextRich Return "text/richtext" Case MediaTypes.TextXml Return "text/xml" Case Else Return "application/octet-stream" End Select End Function '========================================================================== '========================================================================== '******************************************************************************* ' Enum TransferEncodings: Structure used by GetTransferEncoding '******************************************************************************* Public Enum TransferEncodings As Integer QuotedPrintable ' 0 = Integer value Base64 ' 1 SevenBit ' 2 End Enum '******************************************************************************* ' Function Name : GetTransferEncoding ' Purpose : Provide easy access to System.Net.Mime.TransferEncoding data ' : ' Returns : Provided a TransferEncodings value, a TransferEncoding value ' : is returned. '******************************************************************************* Public Shared Function GetTransferEncoding(ByVal TransferEncoding As TransferEncodings) As System.Net.Mime.TransferEncoding Return DirectCast(TransferEncoding, System.Net.Mime.TransferEncoding) End Function '========================================================================== '==========================================================================

Page 57: Send (SMTP) and Retrieve (POP3) Email with Ease under VB.NET

–57–

'******************************************************************************* ' Function Name : GetEmailInfo ' Purpose : Break email down into its component parts. ' : ' Returns : EmailInfo object with component parts of email broken down. ' : ' NOTES : This method uses classes EmailItems and EmailInfo. ' : The Message Body, and each AlternnateView or Attachment are ' : contained within EmailItem objects within the EmailIngo object. ' : ' : An EmailItem contains fields for FROM, TO, SUBJECT, Content-Type, ' : a flag indicating if the ContentTypeData is a filename or if it is ' : text formatting, content-transfer-encoding data, and the raw encoded, ' : data, whether it is a message or binary information. If the content- ' : transfer encoding is set to "base64", the data should be decoded ' : using the DecodeBase64() method. If it is "quoted-printable", the ' : data should be decoded using DecodeQuotedPrintable(). If it is ' : "7bit", it is 7-bit data and does not need to be decoded. '******************************************************************************* Public Shared Function GetEmailInfo(ByVal MailMessage As String) As EmailInfo Dim Info As New EmailInfo 'structure to hold breakdown of email Dim Ary() As String = Split(MailMessage, vbCrLf) 'break full email into lines Dim Idx As Integer = 0 'index into Ary() Dim MX As Integer = UBound(Ary) + 1 'find end if list+1 Dim Boundaries As New Collections.Generic.List(Of String) 'boundary definitions Dim IsMultiPart As Boolean = False 'true if we have multiple parts Dim SeekingEncoding As Boolean = False 'true if we are looking for encoding Dim BuildingDataBlock As Boolean = False 'true if we are building a data block Dim HaveMessageBody As Boolean = False 'true if we have the message body defined Dim ContentType As String = vbNullString 'hold last-defined Content Type Dim ContentTypeIsName As Boolean = False 'true of Content Type specified a file Dim ContentTypeData As String = vbNullString 'if block isan attachment Dim ContentEncoding As String = vbNullString 'hold last-defined Content Transfer Encoding Dim ContentBody As String = vbNullString 'block data accumulator '----------------------------------------------------------- Dim Inheader As Integer = 4 'flag for gathering To, From, Date, Subject Do Dim S As String = Ary(Idx) 'grab a line of data from the email ' ' check for important header items ' If CBool(Len(S)) AndAlso CBool(Inheader) Then 'if we are currently in the header... Dim I As Integer = InStr(S, ":") 'find field delimiter If CBool(I) Then 'found one? If VB.Right(S, 1) = ";" Then 'line continues? Idx += 1 'yes, so bump index S &= Ary(Idx).Trim(Chr(9), " "c) 'append next line next line End If Select Case LCase(VB.Left(S, I)) 'yes, check for one of 4 fields Case "from:" 'Found FROM field Info.FromData = Trim(Mid(S, I + 1)) 'stuff to structure Inheader -= 1 'drop 1 from flag Case "to:" 'Found TO field Info.ToData = Trim(Mid(S, I + 1)) 'stuff to structure Inheader -= 1 'drop 1 from flag Case "date:" 'Found DATE field Info.DateData = Trim(Mid(S, I + 1)) 'stuff to structure Inheader -= 1 'drop 1 from flag Case "subject:" 'Found SUBJECT field Info.SubjectData = Trim(Mid(S, I + 1)) 'stuff to structure Inheader -= 1 'drop 1 from flag End Select End If If Not CBool(Inheader) Then 'if InHeader flag is zero SeekingEncoding = True 'start looking for a Content-Transfer-Encoding field S = vbNullString 'purge current data End If End If '------------------------------------------------------- ' check for boundaries '------------------------------------------------------- If CBool(Len(S)) AndAlso CBool(Boundaries.Count) Then 'check any defined boundaries For Idy As Integer = 0 To Boundaries.Count - 1 If CBool(InStr(S, Boundaries.Item(Idy), CompareMethod.Text)) Then If BuildingDataBlock Then Dim Itm As New EmailItem 'create a new item Itm.ContentType = ContentType 'store content type Itm.ContentTypeData = ContentTypeData 'save filename or character set Itm.ContentTypeDataIsFilename = ContentTypeIsName 'save flag indicating if Attachment Itm.ContentEncoding = ContentEncoding 'store encoding Itm.ContentBody = ContentBody 'store data ContentBody = vbNullString 'reset accumulator If HaveMessageBody Then 'already have a message body? If ContentTypeIsName Then 'if an attachment Info.Attachments.Add(Itm) 'add an attachment Else 'otherwise an alternate view Info.AlternateViews.Add(Itm) End If Else Info.MessageBody = Itm 'else stuff new item to message body HaveMessageBody = True 'indicate we now have a message body End If ContentTypeData = vbNullString 'reset filename/charset BuildingDataBlock = False 'turn off building flag End If SeekingEncoding = True 'turn block seeing on again S = vbNullString 'purge current data Exit For End If Next End If '------------------------------------------------------- ' build data block '------------------------------------------------------- If BuildingDataBlock Then ContentBody &= S & vbCrLf 'add a line to content data End If '------------------------------------------------------- ' if seeking encoding '------------------------------------------------------- If CBool(Len(S)) AndAlso SeekingEncoding Then 'are we seeking TCE? Dim I As Integer = InStr(S, ":") 'yes, check for field delimiter If CBool(I) Then 'did we find one? Select Case LCase(VB.Left(S, I)) 'yes, check for types '======================================================= Case "content-type:" 'Content type? ContentType = Mid(S, I + 1).Trim(Chr(9), " "c) 'yes, so grab data If VB.Right(S, 1) = ";" Then 'more to add?

Page 58: Send (SMTP) and Retrieve (POP3) Email with Ease under VB.NET

–58–

Idx += 1 'yes, so bump index ContentType &= Ary(Idx).Trim(Chr(9), " "c) 'grab next line End If ContentTypeIsName = False 'init flag specifying a file as false Dim sbAry() As String = Split(ContentType, ";") 'now check the content type data ContentType = sbAry(0) 'keep first part for ContentType If StrComp(VB.Left(sbAry(0), 10), "multipart/", CompareMethod.Text) = 0 Then 'multipart, so grab second parameter (boundary definition), and strip any quotes Dim Bnd As String = Trim(Mid(sbAry(1), InStr(sbAry(1), "=") + 1)).Replace("""", vbNullString) Boundaries.Add(Bnd) 'and add a boundary ElseIf StrComp(VB.Left(sbAry(1), 5), "name=", CompareMethod.Text) = 0 Then ContentTypeIsName = True 'attachment if a filename specified (otherwise a view) sbAry = Split(sbAry(1), "=") 'multipart, so grab second parameter 'get second part of second parameter (filename definition) ContentTypeData = sbAry(1).Trim().Replace("""", vbNullString) 'strip any quotes Else ContentTypeData = sbAry(1) 'AlternateView, so stuff display character set If Len(Trim(Ary(Idx + 1))) = 0 Then 'if next line blank, assume ContentEncoding=base64 ContentEncoding = "base64" 'is blank, so assume base64 SeekingEncoding = False 'turn off seeking flag BuildingDataBlock = True 'turn on building data block flag Idx += 1 'bump to skip the blank line End If End If '=================================================== Case "content-transfer-encoding:" ContentEncoding = Mid(S, I + 1).Trim(Chr(9), " "c) 'yes, so grab data SeekingEncoding = False 'turn off seeking flag BuildingDataBlock = True 'turn on building data block flag Idx += 1 'bump to skip required following blank line End Select End If End If Idx += 1 'bump array index Loop While Idx < MX '----------------------------------------------------------- Return Info 'return with filled data block End Function End Class '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ '******************************************************************************* ' Class Name : EmailItem (used by EmailInfo class) ' Purpose : Stores structure of an email block '******************************************************************************* Public Class EmailItem Public ContentType As String = vbNullString 'CONTENT-TYPE data Public ContentTypeData As String = vbNullString 'filename or text encoding Public ContentTypeDataIsFilename As Boolean = False 'True if ContentTypeData specifies a filename Public ContentEncoding As String = vbNullString 'CONTENT-TRANSFER-ENCODING data Public ContentBody As String = vbNullString 'raw data of block End Class '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ '+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ '******************************************************************************* ' Class Name : EmailInfo (used by GetEmailInfo method) ' Purpose : Store component parts of an Email '******************************************************************************* Public Class EmailInfo Public FromData As String = vbNullString 'FROM: Public ToData As String = vbNullString 'TO: Public DateData As String = vbNullString 'DATE: Public SubjectData As String = vbNullString 'SUBJECT: Public MessageBody As EmailItem 'contents of message body Public AlternateViews As New Collections.Generic.List(Of EmailItem) 'list of alternate views Public Attachments As New Collections.Generic.List(Of EmailItem) 'list of attachments End Class

Conclusion This concludes this article on Internet SMTP and POP3 Email processing, but it is certainly not the end

of what can be done. For example, SMTP and POP3 both inherit from System.Net.Socket.TcpClient.

What does this mean to you, you may ask? It means that you can create your own SMTP Mail object,

and send email the way YOU want to send it. For instance, with the .NET Mail class you cannot send

the main body of an email as Rich Text (you remember that IsBodyRtf option I mused about? You can

add it in your own version of the class), or you cannot send a message body coded 7bit. With your own

Mail class, you could, just as you can in Outlook or Windows Mail.

Actually, the underpinning of such a class is already wrapped up within our POP3 class.

David Ross Goben Contact: [email protected] Please feel free to inform me of any errors or omissions.

Page 59: Send (SMTP) and Retrieve (POP3) Email with Ease under VB.NET

–59–

About the Author

David Ross Goben has been a professional software engineer, a writer, and an obsessive

researcher. Of Jewish descent, he has extensively explored Biblical history, ancient

cultural thinking, and ancient slang for over three decades, which has resulted in his seminal work: A Gnostic Cycle: Exploring the Origin of Christianity. He has written

numerous books, manuals, and magazine articles, many uncredited, or authored under

pen names. He has been involved in computing since long before personal computers

were available even in kit form, he was a contributing Editor to 80 Micro Magazine, and there, co-wrote the Feedback Loop column with Beve Woodbury, under the pen name, Mercedes Silver. His

interests include Cosmology, Quantum Physics, human-machine interaction, the Global Warming Myth, The

Electric Universe Theory, Perpetual Energy Technology, Quartz Technology, Dream Walking, and the study of the bio-mechanical origins of life.

He is currently exploring the possibility that Albert Einstein got his Theory of Special Relativity wrong. Just about

everyone is familiar with his equation, E=MC2, Energy (E) equals the Mass (M) times the speed of light (C)

squared. But this Mass to Energy conversion is only half of the Special Relativity expression.

The other half of Special Relativity involves Velocity (V), and is the calculation of Time Displacement (T):

Einstein calculated E = MC2 / T, which would mean that at the speed of light, an object would attain infinite

volume and infinite mass. Correct me if I am wrong, but even an ordinary mortal like me sees this as ridiculous,

because this would mean that we would attain several quintillion times the volume and mass that exists in the

entire universe (and even that may an infinite understatement). All you have to do is simply ponder it for a moment.

However, reverse the order, where E = T / MC2, and all calculations yield the same results, save one, and that is

that instead of attaining infinite volume and infinite mass at the speed of light, one instead moves into an alternate

universe. What most people do not realize is that the very atoms of our body, at this very moment, are already vibrating at just under the speed of light. Just a tiny push and we would become multidimensional beings. I think

this is why Quantum Physics, which is still nowhere near being a perfect model for planck-scale physics, will,

even so, discover the Far World, or what the ancients called the Underworld or the Hidden World, or most everyone today refers to as the Afterlife or Heaven. Quantum Physics has already demonstrated that nothing in the

universe has ever existed without Consciousness. So, where was Consciousness when the Universe was created

out of literally nothing during a causal Singularity Event? Evidence shows that it existed. And that has been the crux of great debates throughout history.

There is an easy experiment that can prove that Einstein’s Theory of Special Relativity is inverted. Weigh a very

heavy object, and then drop it onto a solid base, such as a concrete floor. Now weight the object again. It will weigh less… for about 20 minutes; at which time its full weight will finally return. Where did the missing mass go to during those 20 minutes? If Einstein’s calculation had been correct, the object would have actually weighed

more after being dropped, not less.

Page 60: Send (SMTP) and Retrieve (POP3) Email with Ease under VB.NET

–60–

The following Visual Basic documents are publicly available at: http://www.slideshare.net/DavidRossGoben, and at Google Docs at: http://docs.google.com/leaf?id=0B_Dj_dKazINlN2JlY2EwMmEtNGUyMy00NzQzLTliN2QtMDhlZTc5NDUzY2E5&sort=name&layout=list&num=50.