Main Page Content
Using files to send emails with IIS, part 1 of 2
Rated 4.2 (Ratings: 12) (Add your rating)
Log in to add a comment
(2 comments so far)
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?
- 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...
- 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...
- 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...
- 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?
- 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...
- 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:- 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?
<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:
- we gotta generate a unique filename
- enumerate through our headers and write it to file (with the filename above)
- 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:
<?php
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)<ceiling then pad with zeros to make ceiling chars long
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.


