[thelist] Friday Freebie Part 2 of 2 (and its a book)

Scott Dexter sgd at ti3.com
Fri Sep 29 18:48:52 CDT 2000


(you might wanna print this off and read it on paper, otherwise your eyes
may go crossways by the end)

<tip type="IIS/SMTP" author="Scott Dexter" email="sgd at thinksafely.org">
Last week (http://lists.evolt.org/archive/Week-of-Mon-20000918/004084.html)
we learned how to create an email in MIME format and write it to disk for
the IIS SMTP server to handle.  After reading through it again, I realize
that I didn't debate the pro's and con's to doing it this way. So after we
see how to add attachments, I have what I see as Pro's and Con's. They're at
the end cause I got lengthy....

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 at thinksafely.com>
Date: Wed, Sep 27 2000 12:17 -0600
To: <sgd at ti3.com>
From: <sgd at thinksafely.com>
Subject: test -- attachment
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="XXXXMESSAGEBOUNDARYXXXX"

--XXXXMESSAGEBOUNDARYXXXX
Content-Type: text/plain; charset="US-ASCII"
Content-transfer-encoding: 7bit

this is a no attachment test
--XXXXMESSAGEBOUNDARYXXXX
--XXXXMESSAGEBOUNDARYXXXX
Content-Type: text/plain; name="reghelp.htm"
Content-Transfer-Encoding: quoted-printable
Content-disposition: attachment; filename="reghelp.htm"

<html>
<head>
<title>Simple page</title>
</head>
<body>blow me</body>
</html>
--XXXXMESSAGEBOUNDARYXXXX--


the first thing to notice is in order to make an attachment, we have to
change the MIME type so the email program on the receiving end knows to
parse it out and separate the attachment from the email body. The MIME type
is named "multipart/mixed" --a generic term that says, "Hey I've got
multiple pieces and each piece may have its own MIME type. Cool. But how do
we separate the pieces? Like so:

<%
Const MIMEBOUNDARY = "XXXXMESSAGEBOUNDARYXXXX"
mimeheader = "Content-Type: multipart/mixed; boundary=""" & MIMEBOUNDARY &
""""
%>

The separator is up to you. Quick tip: Make it lengthy, and convoluted. What
you don't want is a separator string that may actually be found elsewhere in
the file. Next, we get to piece up the contents into email body and
attachment. Looking at the format above, its pretty straightforward:

1) parts are encapsulated in "--" + separator boundaries. The last separator
in the file has trailing "--" to signify it's the last part and the EOF is
coming up
2) No line breaks between parts. Having anything --including line breaks--
between parts will generate an additional attachment when the client reads
it, and it will contain anything you put between separators (including just
line breaks)
3) 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 week:
<%
bodyMIME = "Content-Type: text/plain; charset=""US-ASCII""" & vbNewLine
bodyMIME = 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 text
files (for demonstration), let's worry about just the minimum requirements:
<%
attachMIME = "Content-Type: text/plain; name=""reghelp.htm""" & vbNewLine
attachMIME = attachMIME & "Content-Transfer-Encoding: quoted-printable" &
vbNewLine
attachMIME = 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 set
to "quoted-printable" in order to pass the email from server to server to
your email reader without changing the data. The difference between 7-bit
and quoted printable is that 7-bit encoding is really NO encoding, and it is
understood that the content is already in emailable format (lines 76
characters or less of US-ASCII data). Quoted printable is a way to keep
things intact. The last line is the magic one. We tell the email reader that
this is an attachment, and on top of that, we tell the email reader what to
name it. The cool thing here is that the name attribute in the first line
*does not have to* equal the filename attribute. Well, I found this a cool
thing. But I send out attachments that have to have a date appended to the
filename =)

So after we have our MIME shit in order, we have to actually attach
something, right? Let's slurp in a file from the file system, returning
Empty is it doesn't exist or encounter any errors (like the path not being
found):

<%
Function ReadAttachment(byval filename)
On Error Resume Next
Dim ForAppending,fs,a,logstr
' slurp in an attachment
ForReading = 1
Set fs = CreateObject("Scripting.FileSystemObject")
Set a = fs.OpenTextFile(filename, ForReading)
' first determine if it exists
if a.AtEndOfStream then
	slurped = Empty
else
	slurped = a.ReadAll
end if
a.Close
Set a = Nothing
Set fs = Nothing
ReadAttachment = slurped
End 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 we
don't want the full path name in with the file name. I don't want the
recipient 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 If
StripPath = tmpname
End 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 week and add the changes. Let's make
our boundary string a constant and tweak functions GenMIMEHeaders() and
GenMIMEEmail():

<%
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 &">," & tolist
Next
tolist = Left(tolist,len(tolist)-1) ' take off the last comma
headers = "Return-Path:"&replyto & vbNewLine
headers = headers & "Date: " & GenMIMEDate(Now,"-0600") & vbNewLine
headers = headers & "To:"& tolist & vbNewLine
headers = headers & "From:"& from & vbNewLine
headers = headers & "Subject: "& subject & vbNewLine
headers = headers & "MIME-Version: 1.0" & vbNewLine
headers = headers & "Content-Type: multipart/mixed;
boundary="""&MIMEBOUNDARY&""""
GenMIMEHeaders = headers & vbNewLine & vbNewLine
end function

'********* GenMIMEEmail ************
function GenMIMEEmail(byval from, byval mto, byval subject, byval body,
byval fileattach)
bodyMIME = "Content-Type: text/plain; charset=""US-ASCII""" & vbNewLine
bodyMIME = bodyMIME & "Content-transfer-encoding: 7bit" & vbNewLine &
vbNewLine

fullmail = GenMIMEHeaders(from,from,mto,subject) & "--" & MIMEBOUNDARY &
vbNewLine

' --add the body--
fullmail = fullmail & bodyMIME & body & vbNewLine
fullmail = fullmail & "--" & MIMEBOUNDARY

' Do we need to attach a file?
if isEmpty(fileattach) or fileattach="" then ' Nope, no file, close the
separator
	GenMIMEEMail = fullmail & "--" & vbNewLine
else
' theres 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 & vbNewLine
	
	fullmail = fullmail & vbNewLine & "--" & MIMEBOUNDARY & vbNewLine &
attachMIME
	fullmail = fullmail & ReadAttachment(fileattach) & vbNewLine
	GenMIMEEMail = fullmail & "--" & MIMEBOUNDARY & "--" & vbNewLine
end if
end 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 line
email = GenMIMEEmail("sgd at ti3.com", _
			"sgd at fastlane.net", _
			"New Log", _
			"Here is your log", _
			"D:\logfiles\newestlog.txt")

' without an attachment:
email = GenMIMEEmail("sgd at ti3.com", _
			"sgd at fastlane.net", _
			"New Log", _
			"Here is your log", _
			"")  ' Note we've allowed Empty and "" to signify no
file to attach

writeEmail email,GenMIMEName
%>


To note, the reason I use constructs like
<%
headers = headers & "To:"& tolist & vbNewLine
headers = headers & "From:"& from & vbNewLine
headers = headers & "Subject: "& subject & vbNewLine
%>

 is to keep things from line wrapping in the email, and for readability
(sorta)-- it allows me to explain a portion of logic without having to look
for code hidden inside a long line. In production, you do want to reduce the
redundancy as much as you can, and place that stuff on one line. Or keep
them separated out, whichever you prefer. It is a minor performance hit to
spread 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 it
can be re-used, right? This little project here *screams* to be done in a
COM object, where it is 1) compiled, and runs faster and 2) an object
available for use across your system, even for non web apps. Keep that in
mind as you write lengthy functions and 'classes' in your ASP files.
-------

and now the debate:
Pros
----
1) Performance --Writing to a file may be faster (depending on the run-time
environment) than talking to a mail server, especially when you may not be
able to control the availability/response time of the mail server. On top of
that, if the attachment is large, your page has to sit and wait for the mail
server to slurp in all the data. Its not much slower than our
reading-and-rewriting, but something to consider. --Add in multiple
recipients and you see where this can get out of hand and we should just
treat this as

2) Asynchronous --Let's say we have to send out 2 emails. Using CDONTS or a
third party component like ASPMail will be fine. Let's say we have to send
out 2,000. 200,000. 2,000,000? --Having a process (in our examples an ASP
page) connected to a mail server that long (1) isn't efficient, and (2) most
likely won't run to completion in one session because email servers have
limits to the number of messages they'll accept from one connection at one
time (spam discouraging, and while you're connected noone else can). Using
this method, we can write 10,000 emails to disk and let the mail server get
to them when it can.  We're no longer bound to the SMTP server's performance
and in my mind that makes it

3) Scalable --If you handle *large* amounts of outgoing email, you can
(relatively) easily migrate from the local disk to a shared storage solution
or something of the sort to handle the file I/O. Keeping things in a uniform
paradigm (dealing with files) as when working with rejected emails and (if
you dare) incoming email in addition to just sending them is a plus to me.
Its easier to administer. And, if you need to handle multiple MIME types, or
send binary files,

4) You can ditch the home-spun code but keep the process. Mabry has an
ActiveX COM object that will handle all your MIME email generation needs,
including writing the output to file (http://www.mabry.com/mailx.htm)

Cons
----
1) Scalability is a concern, but not a show stopper. Say you've got 4 web
servers, and one email server. You're left with using UNC names and drive
shares for the 4 web servers to write to the email server's Pickup
directory, and that introduces permissions headaches, enough to warrant
converting the code into an ActiveX DLL so it can be loaded in MTS where you
can specify the identity under which it executes

2) Maintainability. --got a new MIME type you wanna send? Open the code.
Wanna send a binary file? open the code (our code today does text
attachments). We are kinda re-inventing the wheel here, which is to be
expected when you work on something that others are too. With some spare
cash (probably amounting to less than the amount of billable time you spend
writing it), you can grab ActiveX objects to handle the dirty work for you
(http://www.mabry.com/mailx.htm)

</tip>

First person to make it this far gets a surprise....
sgd
--
work http://www.ti3.com/
non http://thinksafely.org/




More information about the thelist mailing list