Main Page Content
Using Files to Send Emails with IIS, part 2 of 2
Rated 4.13 (Ratings: 6) (Add your rating)
Log in to add a comment
(0 comments so far)
(finally!)
In the first installment we saw how to create emails and drop them off in the file system instead of talking directly to the email server. In this piece, we build on the code and add the ability to send text attachments.
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 -0600 To: <sgd@ti3.com> From: <sgd@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 an 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--
Reconstructing the Crime
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. 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:
- 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
- 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)
- 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""" & 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 schtuff in order, we have to actually attach something, right? Let's slurp in a file from the file system, returning Empty if 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 time 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 ' 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 & 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@ti3.com", _
"sgd@fastlane.net", _
"New Log", _
"Here is your log", _
"D:\logfiles\newestlog.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 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 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.
-------


