Main Page Content
Using Files To Send Emails With Iis Part 2 Of 2
(finally!)
The Evidence
First, let's see what an email file with an attachment looks like as it sits
quietly, waiting for the SMTP server to come by and pick it up:Return-Path:<sgd@thinksafely.com>Date: Wed, Sep 27 2000 12:17 -0600To: <sgd@ti3.com>From: <sgd@thinksafely.com>Subject: test -- attachmentMIME-Version: 1.0Content-Type: multipart/mixed; boundary="XXXXMESSAGEBOUNDARYXXXX"--XXXXMESSAGEBOUNDARYXXXX
Content-Type: text/plain; charset="US-ASCII"Content-transfer-encoding: 7bitthis is an attachment test
--XXXXMESSAGEBOUNDARYXXXX--XXXXMESSAGEBOUNDARYXXXXContent-Type: text/plain; name="reghelp.htm"Content-Transfer-Encoding: quoted-printableContent-disposition: attachment; filename="reghelp.htm"<html>
<head><title>Simple page</title></head><body>blow me</body></html>--XXXXMESSAGEBOUNDARYXXXX--
Reconstructing the Crime
The first thing to notice is in order to make an attachment, we have tochange the MIME type so the email program on the receiving end knows toparse it out and separate the attachment from the email body. The MIME typeis named "multipart/mixed" --a generic term that says, "Hey I've gotmultiple pieces and each piece may have its own MIME type. Cool. But how dowe separate the pieces? Like so:<%Const MIMEBOUNDARY = "XXXXMESSAGEBOUNDARYXXXX"mimeheader = "Content-Type: multipart/mixed; boundary=""" & MIMEBOUNDARY &""""%>
The separator is up to you. Make it lengthy, and convoluted. What
you don't want is a separator string that may actually be found elsewhere inthe file. Next, we get to piece up the contents into email body andattachment. Looking at the format above, its pretty straightforward:- parts are encapsulated in "--" + separator boundaries. The last separatorin the file has trailing "--" to signify it's the last part and the EOF iscoming up
- No line breaks between parts. Having anything --including line breaks--between parts will generate an additional attachment when the client readsit, and it will contain anything you put between separators (including justline breaks)
- redefine the MIME type of each part
For the body of the email (from what I understand the body is always the
first part in a multipart message), we use the MIME type from last time:<%bodyMIME = "Content-Type: text/plain; charset=""US-ASCII""" & vbNewLinebodyMIME = bodyMIME & "Content-transfer-encoding: 7bit" & vbNewLine &vbNewLine%>
For the attachment, there are options. We can attach anything, from a jpeg
file to an Access database. Since we're limiting this to attaching textfiles (for demonstration), let's worry about just the minimum requirements:<%attachMIME = "Content-Type: text/plain; name=""reghelp.htm""" & vbNewLineattachMIME = attachMIME & "Content-Transfer-Encoding: quoted-printable" &vbNewLineattachMIME = attachMIME & "Content-disposition: attachment;filename=""reghelp.htm"""attachMIME = attachMIME & vbNewLine & vbNewLine%>
In the first line I added the name attribute to identify the local filename
(as it was named when the email was created). The transfer encoding is setto "quoted-printable" in order to pass the email from server to server toyour email reader without changing the data. The difference between 7-bitand quoted printable is that 7-bit encoding is really NO encoding, and it isunderstood that the content is already in emailable format (lines 76characters or less of US-ASCII data). Quoted printable is a way to keepthings intact. The last line is the magic one. We tell the email reader thatthis is an attachment, and on top of that, we tell the email reader what toname it. The cool thing here is that the name attribute in the first linedoes not have to equal the filename attribute. Well, I found this a coolthing. But I send out attachments that have to have a date appended to thefilename =)So after we have our MIME schtuff in order, we have to actually attach
something, right? Let's slurp in a file from the file system, returningEmpty if it doesn't exist or encounter any errors (like the path not beingfound):<%Function ReadAttachment(byval filename)On Error Resume NextDim ForAppending,fs,a,logstr' slurp in an attachmentForReading = 1Set fs = CreateObject("Scripting.FileSystemObject")Set a = fs.OpenTextFile(filename, ForReading)' first determine if it existsif a.AtEndOfStream then slurped = Emptyelse slurped = a.ReadAllend ifa.CloseSet a = NothingSet fs = NothingReadAttachment = slurpedEnd Function%>The caveat here is that you have to know the FULL path to the file,otherwise you'll get an empty attachment. The other side of that coin is wedon't want the full path name in with the file name. I don't want therecipient to know my directory structure. So to that end:
<%Function StripPath(ByVal strFilename)' grab the filename from the full path,'basically everything after the last \patharray = Split(strFilename, "\")tmpname = patharray(UBound(patharray))If InStr(tmpname, """") <> 0 Then ' take out double quotes tmpname = Replace(tmpname, """", "")End IfStripPath = tmpnameEnd Function%>
Now that we know what to change, and we've got what we need to attach a
file, lets rip open the code from last time and add the changes. Let's makeour boundary string a constant and tweak functionsGenMIMEHeaders()
andGenMIMEEmail()
:<%Const MIMEBOUNDARY = "XXXXMESSAGEBOUNDARYXXXX"'********* GenMIMEHeaders ************
function GenMIMEHeaders(byval replyto, byval from, byval mto, byval subject)replyto = "<"& replyto &">"from = "<"& from &">"sendto = split(mto,",")for each addr in sendto tolist = "<"& addr &">," & tolistNexttolist = Left(tolist,len(tolist)-1) ' take off the last commaheaders = "Return-Path:"&replyto & vbNewLineheaders = headers & "Date: " & GenMIMEDate(Now,"-0600") & vbNewLineheaders = headers & "To:"& tolist & vbNewLineheaders = headers & "From:"& from & vbNewLineheaders = headers & "Subject: "& subject & vbNewLineheaders = headers & "MIME-Version: 1.0" & vbNewLineheaders = headers & "Content-Type: multipart/mixed;boundary="""&MIMEBOUNDARY&""""GenMIMEHeaders = headers & vbNewLine & vbNewLineend function'********* GenMIMEEmail ************
function GenMIMEEmail(byval from, byval mto, byval subject, byval body,byval fileattach)bodyMIME = "Content-Type: text/plain; charset=""US-ASCII""" & vbNewLinebodyMIME = bodyMIME & "Content-transfer-encoding: 7bit" & vbNewLine & vbNewLinefullmail = GenMIMEHeaders(from,from,mto,subject) & "--" & MIMEBOUNDARY & vbNewLine' --add the body--
fullmail = fullmail & bodyMIME & body & vbNewLinefullmail = fullmail & "--" & MIMEBOUNDARY' Do we need to attach a file?
if isEmpty(fileattach) or fileattach="" then ' Nope, no file, close theseparator GenMIMEEMail = fullmail & "--" & vbNewLineelse' there's an attachment attach = StripPath(fileattach) attachMIME = "Content-Type: text/plain; name="""& attach &"""" & vbNewLine attachMIME = attachMIME & "Content-Transfer-Encoding: quoted-printable" & vbNewLine attachMIME = attachMIME & "Content-disposition: attachment; filename="""&attach&"""" attachMIME = attachMIME & vbNewLine & vbNewLinefullmail = fullmail & vbNewLine & "--" & MIMEBOUNDARY & vbNewLine & attachMIME
fullmail = fullmail & ReadAttachment(fileattach) & vbNewLine GenMIMEEMail = fullmail & "--" & MIMEBOUNDARY & "--" & vbNewLineend ifend function%>
So the finished function calls to get the job done look like this:
<%' in VB and VBScript, we can use the _ to extend to the next lineemail = GenMIMEEmail("sgd@ti3.com", _ "sgd@fastlane.net", _ "New Log", _ "Here is your log", _ "D:\logfilesewestlog.txt")' without an attachment:
email = GenMIMEEmail("sgd@ti3.com", _ "sgd@fastlane.net", _ "New Log", _ "Here is your log", _ "") ' Note we've allowed Empty and "" to signify no file to attachwriteEmail email,GenMIMEName
%>
To note, the reason I use constructs like
<%headers = headers & "To:"& tolist & vbNewLineheaders = headers & "From:"& from & vbNewLineheaders = headers & "Subject: "& subject & vbNewLine%>is for readability(sorta) it allows me to explain a portion of logic without having to lookfor code hidden inside a long line. In production, you do want to reduce theredundancy as much as you can, and place that stuff on one line. Or keepthem separated out, whichever you prefer. It is a minor performance hit tospread it out like this, but we're not concentrating on that today =)
So that's that. We're sending emails, and even attachments if we're feeling
saucy.-------Sidebar
You've read about n-tier applications and separating "business logic" so itcan be re-used, right? This little project here *screams* to be done in aCOM object, where it is 1) compiled, and runs faster and 2) an objectavailable for use across your system, even for non web apps. Keep that inmind as you write lengthy functions and 'classes' in your ASP files.
-------