Skip to main content
Ben Nadel at cf.Objective() 2012 (Minneapolis, MN) with: Matt Graf and Nolan Erck
Ben Nadel at cf.Objective() 2012 (Minneapolis, MN) with: Matt Graf Nolan Erck

Cleaning Up And Sending One-Off Undelivered ColdFusion Emails

By
Published in Comments (16)

When I'm developing a ColdFusion application on my local development environment, I'll usually define invalid SMTP (Simple Mail Transfer Protocol) credentials so that the CFMail tag doesn't actually send out mail. Instead, ColdFusion ends up dumping the outgoing mail items into the "Undelivr" folder. Then, I'll use the "Undelivered Mail" module in the ColdFusion administrator to view these undelivered mail items. Unfortunately, this approach provides a somewhat skewed representation of how the outgoing mail items will actual render in email clients. As such, I like to occasionally take one of the undelivered mails items and send it out to my various email inboxes.

Rather than temporarily changing out the SMTP credentials in the ColdFusion administrator (or in my per-Application SMTP server settings), I simply grab the most recent undelivered email item, clean it up, and then send it out using new credentials. While this concept is simple, cleaning up the ".cfmail" file content requires some Regular Expression replacement; as such, I thought I'd share my little stand-alone, one-off sender.

<cfscript>

	// Query for the most recently undelivered mail definition. Since
	// we can't actually limit to a single file, we will order it
	// based on date file was created - newest ones first.
	// --
	// NOTE: The location of the undelivered mail folder (Undelivr)
	// will depend on your installation and ColdFusion configuration.
	mailItems = directoryList(
		"/Applications/ColdFusion10/cfusion/Mail/Undelivr/",
		false,
		"query",
		"*.cfmail",
		"dateLastModified DESC"
	);

	// If there is no mail, just exit.
	if ( ! mailItems.recordCount ) {

		writeOutput( "No undelivered mail found." );
		exit;

	}

	// Read in the content of the most recent undelivered mail item.
	// Note that this is NOT the raw content of the CFMail tag; rather,
	// it contains additional meta data about the mail. The content
	// will look somethign like this:
	// --
	// type: text/html
	// server: ...
	// from: ...
	// to: ...
	// replyto: ...
	// subject: ...
	// X-Mailer: ...
	// body: This is some mail content.
	// body: This is some mail content.
	// body: This is some mail content.
	content = fileRead( "#mailItems.directory#/#mailItems.name#" );

	// Find the type of mail content (HTML vs. Plain). If we don't
	// find "text/html", we're going to assume Plain text.
	isHtml = !! reFind( "(?mi)^type:\s*text/html", content );

	// Strip out all the meta-data before the mail content.
	content = javaCast( "string", content ).replaceAll(
		javaCast( "string", "(?mi)^(?!body:)[^\r\n]*[\r\n]" ),
		javaCast( "string", "" )
	);

	// Strip out all the per-line "body:" content markers.
	content = javaCast( "string", content ).replaceAll(
		javaCast( "string", "(?mi)^body:\s*" ),
		javaCast( "string", "" )
	);

	// Send out the mail using LIVE mail server credentials.
	new Mail().send(
		to = "ben@bennadel.com",
		from = "ben@bennadel.com",
		subject = "TEST EMAIL",
		body = content,
		type = ( isHtml ? "html" : "plain" ),
		spoolEnable = false,
		server = "************",
		port = "****",
		username = "************",
		password = "************"
	);

	writeOutput( "Mail Sent, check your inbox!" );

</cfscript>

This ColdFusion script strips out the pre-content metadata as well as the "body:" content markers. It then creates a new CFMail tag (via CFScript) and re-sends the email using valid credentials. It's simple, but it has been a huge time-saver for testing the rendering quirks in various email clients while maintaining a siloed development environment.

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

Reader Comments

1 Comments

Ben, in the line below, is the "!!" a typo or am I missing something?

isHtml = !! reFind( "(?mi)^type:\s*text/html", content );

15,841 Comments

@Bob,

Good questions. It's intentional. The !! "operator" converts the reFind() result to a more "correct" boolean value. Since reFind() returns the index of the match within the string, the "!!" converts it from a number to "True."

Imagine that the reFind() result was 18. Then:

! 18 => False
!! 18 => ! False => True

It's not really necessary since ColdFusion treats any non-Zero number as a Truthy. It's just something I've picked up in the JavaScript world.

7 Comments

Sounds like a lot of work.

I just test for the server IP address. If is my local dev box send ME any emails (so I can ensure all working correctly).

If a production server use settings specific to that machine.

1 Comments

I've generally taken the approach of a function, or you could make a custom tag, that handles cfmail calls. Within the function I do a check for production vs non-production. If it's not the production server I replace the TO field with the currently logged in users e-mail and get rid of the CC field.

I then append to the e-mail something of "This e-mail was sent to you because it was not triggered in production. In production this would normally go to the following:"

There I'd have it display the TO and CC values and then right after that just have an <hr> tag or something. This lets me test everything and also ensure e-mails are also going around where they should be as I'm going through tests without having to modify any code or mess with the mail messages. This e-mail function is also where I let myself use sort of quick variables of like [webUrl] which will get replaced with the valid web URL for whichever environment I'm in and other such things.

15,841 Comments

@Nick,

That's an interesting approach. Though, it seems like you would really need to plan ahead from something like that. One thing that I really enjoy about my approach is that it doesn't actually require any updates to the App code itself.

7 Comments

@Ben,

In Application.cfm like this:

<cfscript>
Request.ExecutionEnvironment=Trim(GetServerIP());
</cfscript>

<cfscript>
switch(Request.ExecutionEnvironment) {
case "127.0.0.1":
WriteOutput("Using LOCAL server settings");
break;
case "stage.mycompany.com":
WriteOutput("Using STAGING server settings");
break;
default:
WriteOutput("Using LOCAL server settings");
}
</cfscript>

//GetServerIP() is a UDF. Using request scope as it is available all the way to OnRequestEnd.cfm.

15,841 Comments

@Peter,

Right, but how do you actually make sure that emails get delivered to You as opposed to an intended user. For example, imagine that I had to send some email to a user, "ben@example.com". Well, if I have no SMTP setup, then I don't have to care whether or not "example.com" actually exists. But, if I DO have a valid SMTP setup, then I have to be careful.

And, I think what you're saying is that if you're on the local machine, you make sure to set the "TO" address to be a single user, rather than the user defined in the business logic?

9 Comments

I do something similar to Peter, all of our applications have a server mode defined in a configuration file.

Application.ServerMode = "LOCAL"
Application.ServerMode = "ACCEPTANCE"
...
Application.ServerMode = "PROD"

I combined that with a custom tag so I only had to change all my old cfmail to cf_mail.

<cfmail from="..." to="..." subject="...">
Stuff
</cfmail>
 
Then become
 
<cf_mail from="..." to="..." subject="...">
Stuff
</cf_mail>

With CF's attribute collections this all get passed down to your custom tag in a nice structure that you can play with based on the value of Application.ServerMode.

It messes up with cfmailparams a bit, but I pass them to the custom tag in an array that I expand into <cfmailparams... >.

I could post the full tag if anyone is interested.

7 Comments

I think you are over complicating it. I just check for server IP.

Nut what ever works.

I discover today in a V2 update for The Jungle that if you neglect to apply the rules - you "The quick brown fox jumped over the lazy dog" soon gets an official response from the help desk software. Doh!

For an update to Bootstrap 3 and the latest in icons from AwesomeFonts. Worth it.

7 Comments

Problem I have found is you can't say CFSWITCH the opening CFMAIL tag. You have to encapsulate EVERTHING. Which is bullshit but I gues how the Java engine works.

Tip to Adove allow us to at least CFSWITCH the opening CFMAIL tag to allow for this.

7 Comments

My point is in development anything going out comes to me. I become the CC. Yes I know about BCC bane of any developer.

The result is immediate and I am the TO recipient rather than my companies system help desk - at least under development.

How many times they ring you back "is this real"
hence the Lazy Dog. These are existing systems. So to test out of the circle is a good thing. When the site goes LIVE you hope that your code is up to it.

"Test message please ignore" can avoid it mostly until site goes LIVE then you cannot help yourself to a) be the first or b) test.

15,841 Comments

I can dig it. At least we all have a way to do it. I think it's funny that we all have basically the same check for server "environment" :)

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