Main Page Content
Using Files To Send Emails With Iis Part 1 Of 2
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 fastsmooth asynchronous way: drop a file off and let the SMTP server handleit.Why?
- Performance Writing to a file may be faster (depending on the run-timeenvironment) than talking to a mail server, especially when you may not beable to control the availability/response time of the mail server. On top ofthat, if the attachment is large, your page has to sit and wait for the mailserver to slurp in all the data. Its not much slower than ourreading-and-rewriting, but something to consider. --Add in multiplerecipients and you see where this can get out of hand and we should justtreat this as...
- Asynchronous Let's say we have to send out 2 emails. Using CDONTS or athird party component like ASPMail will be fine. Let's say we have to sendout 2,000. 200,000. 2,000,000? Having a process (in our examples an ASPpage) connected to a mail server that long (1) isn't efficient, and (2) mostlikely won't run to completion in one session because email servers havelimits to the number of messages they'll accept from one connection at onetime (spam discouraging, and while you're connected noone else can). Usingthis method, we can write 10,000 emails to disk and let the mail server getto them when it can. We're no longer bound to the SMTP server's performanceand in my mind that makes it...
- Scalable If you handle large amounts of outgoing email, you can(relatively) easily migrate from the local disk to a shared storage solutionor something of the sort to handle the file I/O. Keeping things in a uniformparadigm (dealing with files) as when working with rejected emails and (ifyou 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, orsend binary files...
- You can ditch the home-spun code but keep the process. Mabry has anActiveX 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?
- Scalability is a concern, but not a show stopper. Say you've got 4 webservers, and one email server. You're left with using UNC names and driveshares for the 4 web servers to write to the email server's Pickupdirectory, and that introduces permissions headaches, enough to warrantconverting the code into an ActiveX DLL so it can be loaded in MTS where youcan specify the identity under which it executes, and this can also lead to problems with...
- Maintainability. Got a new MIME type you wanna send? Open the code.Wanna send a binary file? open the code (our code here does textattachments). We are kinda re-inventing the wheel here, which is to beexpected when you work on something that others are too. With some sparecash (probably amounting to less than the amount of billable time you spendwriting 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:- IIS 4
- IIS' SMTP Server installed on the same server
- ASP and VBScript (Part 3 could be putting this stuff into a component (dll), ask me)
- a little MIME knowledge
Working backwards, let's start with a little MIME, shall we?
MIME (Multi-purpose Internet Mail Extensions) is an extension of SMTP
(Simple Mail Transfer Protocol), that allows some description of the data tocue the recipient into using the right application to view the contents. Youmay have already dealt with MIME types and not even know it. "text/html"anyone? how about "text/css" ? --Those are MIME types, and tell therecipient (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 inraw 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 tosend attachments, we have to play with MIME and know the right way topackage it.Email process overview:
- we gotta generate a unique filename
- enumerate through our headers and write it to file (with the filenameabove)
- we move that file to the SMTP Pickup directory, where the SMTP Servernabs 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 -0600To: <sgd@ti3.com>From: <support@ti3.com>Subject: [Friday Freebie] Look, ma no CDONTS!MIME-Version: 1.0Content-Type: text/plain; charset="US-ASCII"Content-transfer-encoding: 7bitThis 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 wehave at least one blank line after the headers to indicate the start of thebody. To create the date, we need to spit it out in "ddd, dd mmm yyyy hh:nngggg" 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 &" "& ggggend functionFunction zeropad(byval val, byval ceiling)if len(val)for i=1 to ceiling-Len(val) val = "0"&val Nextend ifzeropad = valEnd 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 &">," & 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: text/plain; charset=""US-ASCII""" &vbNewLineGenMIMEHeaders = headers & "Content-transfer-encoding: 7bit" & vbNewLine &vbNewLineend functionfunction GenMIMEEmail(byval from, byval mto, byval subject, byval body)
GenMIMEEmail = GenMIMEHeaders(from,from,mto,subject) & bodyend function%>
Notice we're able to take in a comma delimited list of To:
addresses. That
Also notice I've separated creating the headers with attaching the body.
This on purpose, because in the next part we're gonnaexpand 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 slurpit up before we're done, and that's Bad. Oh yeah, where the hell is thisPickup directory? \InetPub\MailRoot\Pickup
(using the default install namefor the Inet root). Create a directory for the temp spot; we useD:\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 fileForAppending = 8filename = "D:\InetPub\Mailroot\Pickup\tempmail\" & filenameSet fs = CreateObject("Scripting.FileSystemObject")Set a = fs.OpenTextFile(filename, ForAppending, True)a.Write(email)a.CloseSet a = NothingSet fs = NothingEnd 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 wegotta move it to the Pickup directory to actually get it sent. I'll reopenthe 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 filetempdir ="tempmail\"pickupdir = "D:\InetPub\Mailroot\Pickup\"ForAppending = 8tempfilename = pickupdir & tempdir & filenameSet fs = CreateObject("Scripting.FileSystemObject")Set a = fs.OpenTextFile(tempfilename, ForAppending, True)a.Write(email)a.CloseSet a = Nothingfs.MoveFile tempfilename, pickupdir & filenameSet fs = NothingEnd Sub%>
Phew.
Now, let's see the function calls in action:
<%' use _ to use multiple lines in VBScriptemail = 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.