Skip to main content
Ben Nadel at cf.Objective() 2012 (Minneapolis, MN) with: Ed Northby
Ben Nadel at cf.Objective() 2012 (Minneapolis, MN) with: Ed Northby

Ask Ben: Upload And Email File Using ColdFusion

By
Published in , Comments (41)

I have been asked several times for a good file upload and email tutorial in ColdFusion and I have usually pointed people towards partial solutions cause I didn't have anything great to show them. Then, when I saw Will Peavy ask about this topic over on the CF-Talk list, I figured, I should just sit down and write an example that I can send people to view.

For those of you have been doing ColdFusion for a while, this will be review, but for the beginners out there, this is a fairly comprehensive example on how to use ColdFusion to upload a file, validate data, keep the file system clean, and then send that file to a given email address as a mail attachment. This ColdFusion example uses a lot of error handling. In fact, this is more error handling than I usually do (shame shame), but it is in this example to really point out the places where errors might occur and how you might want to approach handling them.

This example has a form where a web user can enter their name, email and upload a resume file. The file can be either a PDF, DOC, or RTF file type. Once uploaded, an alert is sent, via email, that the resume has been uploaded.

<!--- Kill extra output. --->
<cfsilent>

	<!--- Param FORM variables. --->
	<cfparam
		name="FORM.name"
		type="string"
		default=""
		/>

	<cfparam
		name="FORM.email"
		type="string"
		default=""
		/>

	<cfparam
		name="FORM.resume"
		type="string"
		default=""
		/>

	<!---
		For the form submission flag, since we are asking
		it to be of type numeric, we have to catch the
		CFParam in case someone has hacked the HTML and
		altered the value being sent (a non-numeric value
		will throw a ColdFusion error).
	--->
	<cftry>
		<cfparam
			name="FORM.submitted"
			type="numeric"
			default="0"
			/>

		<!--- Catch CFParam data type errors. --->
		<cfcatch>
			<cfset FORM.submitted = 0 />
		</cfcatch>
	</cftry>


	<!--- Define an array to catch the form errors. --->
	<cfset arrErrors = ArrayNew( 1 ) />


	<!--- Check to see if the form has been submitted. --->
	<cfif FORM.submitted>

		<!---
			Now that the form has been submitted, we need
			to validate the data.
		--->
		<cfif NOT Len( FORM.name )>

			<cfset ArrayAppend(
				arrErrors,
				"Please enter your name"
				) />

		</cfif>

		<!--- Validate email. --->
		<cfif NOT IsValid( "email", FORM.email )>

			<cfset ArrayAppend(
				arrErrors,
				"Please enter a valid email address"
				) />

		</cfif>

		<!---
			When it comes to validating the resume, we want to
			check to see if they selected one. Then, once they
			selected one, we ONLY want to mess with it if there
			are no errors caused by other form fields.
		--->
		<cfif NOT Len( FORM.resume )>

			<cfset ArrayAppend(
				arrErrors,
				"Please select a resume to upload"
				) />

		<cfelseif ArrayLen( arrErrors )>

			<!---
				The file has been selected, but there are errors
				caused by other parts of the form validation.
				Therefore, we now have a file that is just
				sitting in our temp folder. Delete this file to
				keep a clean server.
			--->
			<cftry>
				<cffile
					action="DELETE"
					file="#FORM.resume#"
					/>

				<cfcatch>
					<!--- File delete error. --->
				</cfcatch>
			</cftry>

		<cfelse>

			<!---
				The resume has been selected and there are no
				other errors caused by Form validation.
				Therefore, we can now deal with the file upload.
				There is a chance that the file upload will
				cause an error, so be sure to wrap all file
				actions in CFTry / CFCatch blocks.
			--->
			<cftry>
				<cffile
					action="UPLOAD"
					filefield="resume"
					destination="#GetTempDirectory()#"
					nameconflict="MAKEUNIQUE"
					/>

				<!---
					Now that we have the file uploaded, let's
					check the file extension. I find this to be
					better than checking the MIME type as that
					can be inaccurate (so can this, but at least
					it doesn't throw a ColdFusion error).
				--->
				<cfif NOT ListFindNoCase(
					"pdf,doc,rtf",
					CFFILE.ServerFileExt
					)>

					<cfset ArrayAppend(
						arrErrors,
						"Only PDF, DOC, and RTF file formats are accepted"
						) />

					<!---
						Since this was not an acceptable file,
						let's delete the one that was uploaded.
					--->
					<cftry>
						<cffile
							action="DELETE"
							file="#CFFILE.ServerDirectory#\#CFFILE.ServerFile#"
							/>

						<cfcatch>
							<!--- File Delete Error. --->
						</cfcatch>
					</cftry>

				</cfif>

				<!--- Catch any file errors. --->
				<cfcatch>

					<!---
						There was some sort of error with the
						file upload. Set the error and then try
						to delete the file.
					--->
					<cfset ArrayAppend(
						arrErrors,
						"There was a problem uploading your resume"
						) />

					<!---
						Try to delete the file. Again, we want
						to use CFTry / CFCatch whenever dealing
						with files, especially if we don't know
						if the file is even at the given path.
					--->
					<cftry>
						<cffile
							action="DELETE"
							file="#CFFILE.ServerDirectory#\#CFFILE.ServerFile#"
							/>

						<cfcatch>
							<!--- File delete errors. --->
						</cfcatch>
					</cftry>

				</cfcatch>
			</cftry>

		</cfif>


		<!---
			Now that we have validated our form data, let's
			check to see if there are any form validation
			errors. Only if there are no errors do w want to
			continue processing the data - otherwise, we want
			to skip this next part and let the form re-render.
		--->
		<cfif NOT ArrayLen( arrErrors )>

			<!--- Create a short hand for the file. --->
			<cfset strFilePath = (
				CFFILE.ServerDirectory & "\" &
				CFFILE.ServerFile
				) />


			<cfmail
				to="ben@xxxxxxxx.com"
				from="#FORM.email#"
				subject="Web Site Resume Submission"
				type="html">

				<p>
					The following resum&eacute; has been
					submitted through the web site on
					#DateFormat( Now(), "mmm d, yyyy" )# at
					#TimeFormat( Now(), "h:mm TT" )#.
				</p>

				<p>
					Name: #FORM.name#
				</p>

				<p>
					Email: #FORM.email#
				</p>

				<p>
					Resum&eacute;: <em>See attached file</em>
				</p>


				<!--- Attach the file. --->
				<cfmailparam
					file="#strFilePath#"
					/>

			</cfmail>


			<!---
				Delete the resume file since we no longer need
				it on the server. HOWEVER, this will only work
				if the emails are NOT getting spooled. If the
				email is getting spooled, then we will end up
				deleting the file before it had a chance to get
				attached to the outgoing email and the mail
				will end up failing after it leaves ColdFusion.

				*** Put back in ONLY if SpoolEnable="no" in
				your CFMail tag.
			--->
			<!---
			<cftry>
				<cffile
					action="DELETE"
					file="#strFilePath#"
					/>

				<cfcatch></cfcatch>
			</cftry>
			--->


			<!---
				At this point, you would probably forward
				the user to another page using something
				like CFLocation.
			--->

		</cfif>

	</cfif>

</cfsilent>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
	<title>Upload And Email A File ColdFusion Example</title>
</head>
<body>

	<cfoutput>

		<!--- Check to see if we have any form errors. --->
		<cfif ArrayLen( arrErrors )>

			<h3>
				Please review the following:
			</h3>

			<ul>
				<cfloop
					index="intError"
					from="1"
					to="#ArrayLen( arrErrors )#"
					step="1">

					<li>
						#arrErrors[ intError ]#
					</li>

				</cfloop>
			</ul>

		</cfif>


		<form
			action="#CGI.script_name#"
			method="post"
			enctype="multipart/form-data">

			<!--- Our form submission flag. --->
			<input type="hidden" name="submitted" value="1" />

			<label for="name">
				Name:

				<input
					type="text"
					name="name"
					id="name"
					value="#FORM.name#"
					/>
			</label>
			<br />

			<label for="email">
				Email:

				<input
					type="text"
					name="email"
					id="email"
					value="#FORM.email#"
					/>
			</label>
			<br />

			<label for="resume">
				Resum&eacute;:

				<input
					type="file"
					name="resume"
					id="resume"
					value="#FORM.resume#"
					/>
			</label>
			<br />

			<input type="submit" value="Upload Resum&eacute;" />

		</form>

	</cfoutput>

</body>
</html>

In this example, I am doing my best to try and clean up the server by deleting files when they are invalid or when ColdFusion errors get thrown. This way, I don't have random garbage files just hanging out, taking up space in my temp folders. In reality, this is probably overkill. The better solution would probably be to have a temp directory within in your site that these files are written to. This temp directory would then be cleaned out every day, or every few days to get rid of the garbage files. I say that this is the "better" solution because:

  1. It will make the code more readable (less code to handle the files means more readable / more maintainable).

  2. It will remove any issues caused by spooling emails.

  3. A temp directory (local to your site) is probably easier to get to for inspection than the GetTempDirectory() directory.

One of the benefits I point out above is that we wouldn't have to worry about email spooling. For those of you who don't know what spooling is, it's basically the way the mail server queues the outgoing mail requests. When ColdFusion executes a CFMail tag, it creates a mail object and puts it in the spool folder (or maybe the mail server does that, not sure). The mail object then sits there. The mail server, when it has time, will go through the spool directory and send out the mail objects. This means that there is a potential (and likely) time delay between when you execute the CFMail tag and when the mail server actually sends out your email.

This is important to understand when we are attaching files to emails. If we try to delete a file attachment after sending out the CFMail, chances are the mail will never get sent. The problem here is that the file doesn't actually get attached until the mail gets sent by the mail server. Therefore, if we delete a file after attaching it, we are likely deleting the file while the mail is still in the spool. Then, when the mail server goes to send out the email, it can't find the file attachment and the outgoing mail will fail.

By putting files into a local temp directory and cleaning it out periodically, we don't have to worry about efficient file deleting. Therefore, we don't have to worry about deleting files right after they were mailed out and we can let the mail sit in the spool without worrying about corrupting the data.

The other option is to turn off spooling for your outgoing email. If you add the attribute:

SpoolEnable="No"

... to your CFMail tag, it will request that the mail server not queue it in the spool. Therefore, once the CFMail tag fully executes, you are guaranteed to have already sent the file and any post-CFMail file deleting will not affect the outgoing mail. Of course, I would say that disabling the spool is probably not a great idea as you might be putting undue stress on the mail server.

Once the file is uploaded, I am validating it against the file extension. I would say that this is a better way than using the accepted mime type attribute of the CFFile tag. MIME types are not always accurate and might end up giving you false-negatives. Furthermore, MIME type checking will cause ColdFusion to throw an error if an unaccepted MIME type is uploaded. This just gives you more error handling to worry about. So, while file extensions can be altered, at least you it won't throw an error and you will have more options to work with when validating.

I like to do the file validation as the last part of the form data validation. I find that this requires less overhead since we won't do any big processing until we know the rest of the form has already been validated.

Once you have validated and processed the form data, you probably want to redirect the user to some sort of confirmation page. I have put that in my code comments, but I did not include any CFLocation example.

Want to use code from this post? Check out the license.

Reader Comments

11 Comments

Ben,

How can we change change this to allow multiple file selection for the upload part? I am trying to create a library application where the web user can upload files to a temp directory. Just like you have done here :), but only allow them to make multiple selection and the ext would be a zip file only as an example.

This way the end user would not have to enter all the contact info multiple times.

Thanks

Jim

BTW I am learning a lot from your blog. Thanks for putting this togethor

15,841 Comments

@Jim,

What you could do is just name the file field with some sort of numeric suffix:

name="file1"
name="file2"
name="file3"

Then, basically the validation process would just loop over those. I will see if I can get a demo together for that sort of thing. Since this gets more complex, it's probably better to start abstracting out some of these functions.

28 Comments

Also, if you're specifying multiple file upload fields you're always guessing at the correct number of fields to offer your users. Is 4 fields too many or too few? To get around that issue, you can use GMail's technique of dynamically creating upload fields as the user needs them, as Pete Freitag mentions in his blog:
http://www.petefreitag.com/item/587.cfm

22 Comments

Ben:

In the code below why do you need to delete the file if the <cfelseif ArrayLen( arrErrors )> is true? The way I read your code, the file is never actually uploaded to the server if there are any errors found in the form field values entered by the user.

<cfif NOT Len( FORM.resume )>

<cfset ArrayAppend(
arrErrors,
"Please select a resume to upload"
) />

<cfelseif ArrayLen( arrErrors )>

<!---
The file has been selected, but there are errors
caused by other parts of the form validation.
Therefore, we now have a file that is just
sitting in our temp folder. Delete this file to
keep a clean server.
--->
<cftry>
<cffile
action="DELETE"
file="#FORM.resume#"
/>

<cfcatch>
<!--- File delete error. --->
<cflog date="yes" file="fileError" text="Had to delete #FORM.resume# since there were other errors in the form." type="information" time="yes">
</cfcatch>
</cftry>

<cfelse>

15,841 Comments

@Bruce,

The file is always uploaded to the server (from my understanding), but it starts out by being uploaded to the server's temp directory. If you try to dump out the FORM.resume value, you will see that it points to a .TMP file on the server. The CFFILE tag is not actually uploading the file - it's really just moving it from the temp directory to the directory you are choosing (and taking care of naming conflicts, modes, and client/server name values).

But, like I said, this is probably overkill. I put it more in there to get people thinking - I never actually delete that. I am not sure how often the temp directory gets cleaned, or if it cleans on server-restart. I just wanted to get people thinking. If you remove it, unless you are doing TONS of file uploads, I doubt it will be an issue.

Of course, I could also be wrong. However, I can tell you that the CFFILE delete there doesn't throw an error, so the file must exist.

22 Comments

Ben:

I don't think the file is actually uploaded to the server until the cffile upload is executed. I did a test of commenting out the cffile delete in that cfelseif block and instead just logged the form.resume value.

I reloaded the page, filled in the form with a bad email address, but selected a valid file. I clicked on the Upload Resume button. I get the error message about the bad email.

When I go to the directory on my server where the form.resume value (that was written to my log file) is supposed to be located, nothing is there, even though I commented out the cffile delete.

Bruce

15,841 Comments

@Bruce,

I am sure that ColdFusion is probably handling this elegantly in some way. However, if I put this right before the CFFILE[ Action = Delete ]:

<cfdump var="#FileExists( FORM.resume )#" />
<cfabort />

It outputs:

YES

So, the file is definitely there at the time the code runs. Now, maybe if you don't use the file, ColdFusion deletes is automatically or something? Not sure.

17 Comments

Hi Ben -

I am using some of this code for a presenter form on my site. Like Jim, and me being a newb, I would like to have multiple file uploads. Could you elaborate a bit further on your response to Jim? I am trying to get users to upload a resume and a presentation file, but only the alst file to be upload gets submitted with the results.

Thanks,
Adam

4 Comments

Hey Guys, I am using the Cf_Fileupload tag for multiple file upload but the cause is the same it just uploads the single last file, not the previous one..

I think the only thing cause it to get the last file is this attributee of:

<cfset strFilePath = (
CFFILE.ServerDirectory & "\" &
CFFILE.ServerFile
) />

1 Comments

Create artcile, thanks!

Just curious how you handle the catch-22 of invalid file names. The user has a file with characters that aren't liked by cfmailparam such as "this+that.doc"

Typically, when I receive an uploaded file, I change the name to the ID number of the database record that holds the information about that file such as 123.doc. That ensure it's valid and when downloading back to the user, I use cfheader to present it with it's original name.

But with CFMAILPARAM, there is no such option to store with one name and send with a different name. So you either have to give the user a meaningless file name like '123.doc' or you have to strip out invalid characters and give them a "modified" file name, which is almost as bad.

Any thoughts? :)

15,841 Comments

@Gary,

That is an interesting question. To be honest, I have not run up against an invalid file name in the CFMailParam tag before; before this, I am not sure I even suspected that that was possible. I will look into this to see if I can come up with anything.

15,841 Comments

@Harry,

I am not sure that I understand. This is an upload form; if you remove the upload functionality, then you kind of defeat the purpose of the form.

1 Comments

Ben,

First, I would like to thank you for creating this tutorial. It is a lifesaver for me! Having said that, this is all new to me. When you have a chance can you advise how to configure the form to work with our server paths?

Thanks in advance for humoring a novice.

17 Comments

If you're attempting to upload an image, it will error out as only PDFs, DOCs, and RTFs are accepted.

3 Comments

ok. i understand that. but what if i dont want to upload an image at all. just to have the form sent to an email without the need of uploading an image.

no image. just send the form by itself with all the content. no image. however. if someone wanted to send it, they could.

can it be optional?

17 Comments

remove this section:

# <cfif NOT Len( FORM.resume )>
#
# <cfset ArrayAppend(
# arrErrors,
# "Please select a resume to upload"
# ) />

17 Comments

Hi Ben -

In order to make this form function with multiple uploads, would I need to duplicate the code

<cfelseif ArrayLen( arrErrors )>

<!---
The file has been selected, but there are errors
caused by other parts of the form validation.
Therefore, we now have a file that is just
sitting in our temp folder. Delete this file to
keep a clean server.
--->
<cftry>
<cffile
action="DELETE"
file="#FORM.resume#"
/>

<cfcatch>
<!--- File delete error. --->
</cfcatch>
</cftry>

<cfelse>

<!---
The resume has been selected and there are no
other errors caused by Form validation.
Therefore, we can now deal with the file upload.
There is a chance that the file upload will
cause an error, so be sure to wrap all file
actions in CFTry / CFCatch blocks.
--->
<cftry>
<cffile
action="UPLOAD"
filefield="resume"
destination="#GetTempDirectory()#"
nameconflict="MAKEUNIQUE"
/>

<!---
Now that we have the file uploaded, let's
check the file extension. I find this to be
better than checking the MIME type as that
can be inaccurate (so can this, but at least
it doesn't throw a ColdFusion error).
--->
<cfif NOT ListFindNoCase(
"pdf,doc,rtf",
CFFILE.ServerFileExt
)>

<cfset ArrayAppend(
arrErrors,
"Only PDF, DOC, and RTF file formats are accepted"
) />

<!---
Since this was not an acceptable file,
let's delete the one that was uploaded.
--->
<cftry>
<cffile
action="DELETE"
file="#CFFILE.ServerDirectory#\#CFFILE.ServerFile#"
/>

<cfcatch>
<!--- File Delete Error. --->
</cfcatch>
</cftry>

</cfif>

<!--- Catch any file errors. --->
<cfcatch>

<!---
There was some sort of error with the
file upload. Set the error and then try
to delete the file.
--->
<cfset ArrayAppend(
arrErrors,
"There was a problem uploading your resume"
) />

<!---
Try to delete the file. Again, we want
to use CFTry / CFCatch whenever dealing
with files, especially if we don't know
if the file is even at the given path.
--->
<cftry>
<cffile
action="DELETE"
file="#CFFILE.ServerDirectory#\#CFFILE.ServerFile#"
/>

<cfcatch>
<!--- File delete errors. --->
</cfcatch>
</cftry>

</cfcatch>
</cftry>

</cfif>

in order to validate each uploaded file?

Thanks,
Adam

15,841 Comments

@Adam,

You could probably just create a list with the file field names and then loop over it:

<cfloop index="fieldName" list="upload1,upload2">

... form[ fieldName ]

</cfloop>

Then, you'd only need the logic once and it would be executed for each file upload.

32 Comments

Hello Ben, thanks for writing this tutorial. I have a question: can I use CFFILE.ServerFileExt to change the extension?

I'm uploading PDFs files which I would need to open with Illustrator later. So changing the file extension will solve this issue.

Thanks in advance.

Dani

3 Comments

Hi,
I have tried to make a multiple upload, the first required, the second not but I allways get only one file emailed...
Has anybody got it working with more then one uploads..??
Greetz,
W.A.

1 Comments

Hey Ben!

This form is really outtasite! Thanks so much for helping a cf noob get started. I have a problem customizing your form, though.

I want to add a text area where one can enter a cover letter (this will become the body of the email sent to me.) But I cannot get it to validate. I tried adding:

value="FORM.cover" to the <textarea> tag, but that is really not a property of the tag.

Even so, I am unclear about how to set up the <cfif> tag for validation (perhaps this is evidence of an even deeper ignorance on my part.)

Any slight push in the right direction would be much appreciated!

-Tony

15,841 Comments

@Dani,

Once the file is saved to the server, after the CFFile / Upload action has taken place, you can use the CFFile tag to rename the file or use fileMove() method to rename it with a different extension. So, yes, you could use the ServerFileExt to change it (or rather to skip that part):

fileMove(
(uploadDirectory & CFFile.ServerFile),
(uploadDirectory & CFFile.ServerFileName & ".ai")
)

... I am, of course, making up some variables here (uploadDirectory), but the general idea is there.

@Tony,

I am not sure what you are trying to do. If you give the Textarea a NAME attribute, its value will be submitted in the FORM scope when the user submits the form. But, I am not sure what kind of validation you are trying to do.

1 Comments

I would also like to not require an attachement, I tried removing the code mentioned earlier in the thread but it does not seem to work. Ideas?

3 Comments

Ben,

Your code is brilliant and covers almost everything. But what if len(form.resume) is greater that zero, however, the user just types some junk in the file field instead of attaching an actual file ?

I tried this but ColdFusion totally skipped any processing as if nothing had been posted to the page. How do you check if the user has attached a valid file ?

Thanks in advance.
Alex

15,841 Comments

@Alex,

The "form.resume" value does not hold the value of the actual form field; rather, it holds the TMP file path of the file that has been uploaded. If someone selects an invalid file (or types data into the file field), I *believe* that either the web browser or the server will ignore it.

It sounds like this is what is happening. An invalid file was selected and the len(..) request failed as if not file was selected. So, to answer your question, a "valid" file was selected if that field has a length.

I put valid in quotes as there are other reasons a file might be invalid, but those are specific to your business logic.

1 Comments

Great article with outstanding points. With 30 years experience in the HR industry, I remain excited by the many new and innovative ways to orchestrate the job / career hunt.

17 Comments

Hi Ben -

I have successfully used a variation of your code to complete my task. I am wondering, however, if there is something with docx files that will not pass the error checking:

<cfif NOT ListFindNoCase(
"pdf,doc, docx, rtf",
CFFILE.ServerFileExt
)>

Simply adding the docx extension will not process for me. Am I missing something you can clue me in on please?

Thanks.

17 Comments

Okay - I've double checked this time. What I want to accomplish is to have the user's file renamed to a designated name once it passes validation. The validation works in checking that the form field is not empty nor contains a file that is not designated. However, using the following code, the file still gets uploaded:

<cftry>
	<cffile
		action="UPLOAD"
		filefield="nominationLetter"
		destination="#destination#"
		nameconflict="MAKEUNIQUE"
	/>
<!--- Create variable to reference the original document name and extension uploaded from client.--->
	<cfset clientNominationLetter = #file.ClientFile#>
<!---Create variable to reference renamed document.  Retain orignal file extension.--->
	<cfset renameNomination = "01_nominationLetter"&"."&#cffile.clientFileExt#>
		 
<!---Rename uploaded document using variable.  Save renamed document to original destination.--->
	<cffile action="rename"
				source="#destination##File.ServerFile#"
				destination="#destination##Trim(renameNomination)#">

Within the cftry blocks, I've replaced

file="#CFFILE.ServerDirectory#\#CFFILE.ServerFile#"

with

file="#destination##File.ServerFile#"

which is my designated file location. What am I missing?

Thanks.

17 Comments

Hi -

I'm trying to use this method to validate and upload multiple files. Is the best approach to replicate the code for handling one for each and every other file I want uploaded and rename the elements accordingly?

Thank you.

1 Comments

You are my hero! Even years after you created this post, you are helping others.

I had not figured out how to send a form file attachment by email and then I found this. I modified it only a tweak, and wallah!

Thank you so much!

2 Comments

Hi Ben,

I am new to cold fusion and have been asked to create a cold fusion form which emails three attachments. Your code works brilliantly but I cannot get three files to send.

Could you send me the code on how to do this or point me in the right direction. I am aware you have answered this further up but I cannot figure it out.

Kind regards,

Martin.

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel