Don't use CDONTS (even the acronym is a good reminder). Don't use ASPMail.

Don't worry about talking to the email server directly. Do it the fast

smooth —asynchronous— way: drop a file off and let the SMTP server handle

it.

Why?

  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).

Why not?

  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, and this can also lead to problems with...
  2. Maintainability. —Got a new MIME type you wanna send? Open the code.

    Wanna send a binary file? open the code (our code here 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 this), you can grab ActiveX objects to handle the dirty work for you

    (http://www.mabry.com/mailx.htm).

That being said, we use this process to handle our emails. Its robust, dern fast, and we send a ton

of emails this way.

The recipe

Here's what you need to cook this up:

Working backwards, let's start with a little MIME, shall we?

<insert image of midget in white face-paint trying to get out of an

invisible box>

MIME (Multi-purpose Internet Mail Extensions) is an extension of SMTP

(Simple Mail Transfer Protocol), that allows some description of the data to

cue the recipient into using the right application to view the contents. You

may have already dealt with MIME types and not even know it. "text/html"

anyone? how about "text/css" ? --Those are MIME types, and tell the

recipient (your browser) how to handle the data.

What does knowing MIME do for us? Well, in order to just drop a file off and

go, and expect IIS' SMTP server to send it out, we have to write the file in

raw MIME format. It's not that tough, but ya gots to know what goes where.

—We get to generate our own email headers. On top of that, if we want to

send attachments, we have to play with MIME and know the right way to

package it.

Email process overview:

  1. we gotta generate a unique filename
  2. enumerate through our headers and write it to file (with the filename

    above)
  3. we move that file to the SMTP Pickup directory, where the SMTP Server

    nabs it

Creating a unique filename

   Because we may have multiple people firing this off, we need to

ensure we don't step on anyone else:

<%

Function GenMIMEName()

Randomize ' Initialize random-number generator.

RandomNbr = Int((1000000 * Rnd) + 0)

RandomNbr = Right("0000000" + RandomNbr, 6)

GenMIMEName = Request.ServerVariables("REMOTE_ADDR") & "_" & RandomNbr & ".email"

End Function

%>

Enumerate through file

We need to have some specific headers and such, here's what a MIME email

looks like before its sent out:

Return-Path:<support@ti3.com>

Date: Mon, Aug 28 2000 17:41 -0600

To: <sgd@ti3.com>

From: <support@ti3.com>

Subject: [Friday Freebie] Look, ma no CDONTS!

MIME-Version: 1.0

Content-Type: text/plain; charset="US-ASCII"

Content-transfer-encoding: 7bit

This is the body of the email. I'm the company liability!

— So this is pretty straightforward then, right? We gotta worry about a

couple things: creating the date in the right format, and making sure we

have at least one blank line after the headers to indicate the start of the

body. To create the date, we need to spit it out in "ddd, dd mmm yyyy hh:nn

gggg" —try this:

<%

function GenMIMEDate(byval mydate, byval gggg)

' mydate is already a date type, no error checking here!

' gggg is expected to already be in offset from GMT format, i.e. "-0600"

' we need to return ddd, dd mmm yyyy hh:nn gggg

ddd = weekdayname(weekday(mydate),True)

mmm = monthname(month(mydate),True)

' make sure we have a leading zero on single digit dates

dd = zeropad(day(mydate),2)

yyyy = year(mydate)

hh = hour(mydate)

nn = minute(mydate)

GenMIMEDate = ddd &", "& dd &" "& mmm &" "& yyyy &" "& hh &":"& nn &" "& gggg

end function

Function zeropad(byval val, byval ceiling)

if len(val) for i=1 to ceiling-Len(val)

val = "0"&val

Next

end if

zeropad = val

End Function

%>

— I played with dummy proofing the offset, but for now let's just assume

that we already now it, as its not the point of this tip ;)

So here's what I have to generate our headers and email:

<%

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: text/plain; charset=""US-ASCII""" &

vbNewLine

GenMIMEHeaders = headers & "Content-transfer-encoding: 7bit" & vbNewLine &

vbNewLine

end function

function GenMIMEEmail(byval from, byval mto, byval subject, byval body)

GenMIMEEmail = GenMIMEHeaders(from,from,mto,subject) & body

end function

%>

—Notice we're able to take in a comma delimited list of To: addresses. That

way we can send to multiple people at once (from the department of

redundancy department)

Also notice I've separated creating the headers with attaching the body.

This on purpose, because in the next part we're gonna

expand to include attachments.

File handling

Now we gotta write it to a file, in a temporary spot. This is because if we

create the file in IIS's SMTP Server Pickup directory, it will try to slurp

it up before we're done, and that's Bad. Oh yeah, where the hell is this

Pickup directory? —\InetPub\MailRoot\Pickup (using the default install name

for the Inet root). Create a directory for the temp spot; we use

D:\InetPub\MailRoot\Pickup\TempMail.

Let's write it to file, shall we?

<%

Sub WriteEmail(byval email, byval filename)

Dim ForAppending,fs,a,logstr

' drop the email to a file

ForAppending = 8

filename = "D:\InetPub\Mailroot\Pickup\tempmail\" & filename

Set fs = CreateObject("Scripting.FileSystemObject")

Set a = fs.OpenTextFile(filename, ForAppending, True)

a.Write(email)

a.Close

Set a = Nothing

Set fs = Nothing

End Sub

%>

Move the temp file into the Pickup directory

Well, so far we gots the email being generated with all the MIME we need

(for now), and we're writing it to the file system with no probs. Now we

gotta move it to the Pickup directory to actually get it sent. I'll reopen

the WriteEmail subroutine and tweak a few things to get it done:

<%

Sub WriteEmail(byval email, byval filename)

Dim ForAppending,fs,a,logstr

' drop the email to a file

tempdir ="tempmail\"

pickupdir = "D:\InetPub\Mailroot\Pickup\"

ForAppending = 8

tempfilename = pickupdir & tempdir & filename

Set fs = CreateObject("Scripting.FileSystemObject")

Set a = fs.OpenTextFile(tempfilename, ForAppending, True)

a.Write(email)

a.Close

Set a = Nothing

fs.MoveFile tempfilename, pickupdir & filename

Set fs = Nothing

End Sub

%>

Phew.

Now, let's see the function calls in action:

<%

' use _ to use multiple lines in VBScript

email = genMIMEEmail("support@ti3.com", _

"sgd@ti3.com", _

"[Friday Freebie] Look, ma no CDONTS!", _

"This is the body of the email. I'm the company liability!")

WriteEmail email,GenMIMEName

%>

Gee, that was simple, wasn't it? In the next installment, we'll add in attachments.