Skip to page content or skip to Accesskey List.
Search evolt.org
evolt.org login: or register

Work

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)

Want more?

  • More articles in Code
  • More articles by sgd
 
Picture of sgd

Scott Dexter

Member info | Full bio

User since: April 26, 1999

Last login: December 11, 2009

Articles written: 10

(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:

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

The access keys for this page are: ALT (Control on a Mac) plus:

evolt.orgEvolt.org is an all-volunteer resource for web developers made up of a discussion list, a browser archive, and member-submitted articles. This article is the property of its author, please do not redistribute or use elsewhere without checking with the author.