Skip to main content
Ben Nadel at Scotch On The Rocks (SOTR) 2011 (Edinburgh) with: Rob Rawlins
Ben Nadel at Scotch On The Rocks (SOTR) 2011 (Edinburgh) with: Rob Rawlins ( @SirRawlins )

Using ColdFusion Custom Tags To Create An HTML Email DSL In Lucee CFML 5.3.7.47

By on
Tags:

In my tenure at InVision, I've built a lot of HTML-based emails. Creating — and maintaining — HTML-based emails is not fun. It does not "spark joy". Part of this is because there are a large number of email clients that all have different quirks. And, part of this is because our setup uses Handlebars Templating and Node.js modules to "compile" the emails. Why am I using handlebars and node when I'm sitting on top of one of the most powerful web application runtimes available: Lucee CFML? I would love to be writing my HTML-based emails the way that I write all of my HTML-based views: using CFML. As such, I wanted to start playing around with the use of ColdFusion custom tags as means to create a Domain Specific Language (DSL) that could put the joy back into email creation in Lucee CFML 5.3.7.47

View this code in my ColdFusion Custom Tag Emails project on GitHub.

CAUTION: Just because I've created a lot of HTML-based emails in my career, do not think for a moment that this makes me any kind of export. You could easily make an entire career out of just crafting perfect email experiences. It is hard work; there are crazy number of email clients; and, targeting each client requires a lot of jank. I am - at best - mediocre at it. As such, take this post in good measure.

If you are young, you may not remember the "Browser Wars" from the earlier days of the web. Back when Internet Explorer had document.all and Netscape Navigator had document.layers and everyone used <font> tags and <table> tags to construct pages, it was the Wild West. Making sure that your users were seeing what you thought they were seeing required all kinds of conditional hacks, IE-specific HTML Comments, and testing in lots of different browsers and on different machines.

This is why jQuery was so revolutionary. This is why Jonathan Neal wrote a song about jQuery.

Today, the web development of yesteryear is barely recognizable. Today's browsers are powerful beasts that adhere to standards. CSS and JavaScript work together to create amazingly rich, interactive experiences that can delight and astound users. Today's web development is joyful and unbounded.

HTML-based emails, on the other hand, are nothing like modern web development. They still resemble the madness of the early web; only, we don't get to use jQuery; and, we have to code for dozens of clients that are all terrible in their own unique ways.

HTML-based emails require us to fallback to our <table>-based layouts and the inline styles of the early web. Only, on top of that, we have to progressively enhance our emails to target some clients that support modern web constructs. So, essentially, we have to build our emails using 20-year-old technology; but, do it in such a way that we can create better experiences for the email clients that behave more like modern browsers.

It is very challenging! Thank goodness we have services like Litmus that allow us to get screenshots of what our HTML-based emails look like in dozens of email clients across different devices and different operating systems. Without Litmus, I honestly don't know how I would go about building emails.

Because of all of this complexity, I want to try and create a DSL - a Domain Specific Language - for rendering HTML-based emails in ColdFusion Markup using ColdFusion Custom Tags. A DSL is just an abstraction layer that allows us to describe complex concepts using simple language. Essentially, I want a way to "describe" what an HTML-based email should look like without having to worry about the low-level details.

To give you a concrete example, imagine that I was able to put this snippet of ColdFusion Markup into an email in order to render a responsive image grid:

<imageGrid:List>
	<imageGrid:Image src="image.jpg" />
	<imageGrid:Image src="image.jpg" />
	<imageGrid:Image src="image.jpg" />
	<imageGrid:Image src="image.jpg" />
</imageGrid:List>

As you can see, this "Grid" here is very high-level - it's defining what should go in the grid, but it is not defining how that grid is actually constructed. All the low-level implementation details of how the grid works, and how it is made to be responsive, are deferred to the internal mechanics of the various ColdFusion custom tags.

The power of the DSL - and the ColdFusion custom tags - is that even this simple markup allows us to create the following rich HTML-based email experience:

Responsive image grid in an HTML-based email.

Now, if you have your "modern web developer hat" on, you might look at this responsive grid and think to yourself, "Piece-of-cake - it's just some CSS Flexbox and a CSS media query."

Oh my sweet-sweet, naive child ... you wish it were that easy! Yes, some email clients support CSS media queries. But, almost none of them support CSS Flexbox. So, how do you create this experience in a way that progressively enhances for more robust email clients; but, doesn't look like pure jank in older email clients?

The answer: hacks upon hacks upon hacks. But, at least with a ColdFusion Custom Tag DSL abstraction, the author of the email doesn't have to worry about this as much as they might otherwise. To get a sense of the shenanigans that a responsive grid entails, here's the CFML code for my <imageGrid:List> tag:

<!--- Import custom tag libraries. --->
<cfimport prefix="core" taglib="../../core/" />

<!--- Define custom tag attributes. --->
<cfparam name="attributes.margin" type="string" default="none normal" />

<!--- // ------------------------------------------------------------------------- // --->
<!--- // ------------------------------------------------------------------------- // --->

<cfswitch expression="#thistag.executionMode#">
	<cfcase value="start">
		<cfoutput>

			<cfset images = [] />

		</cfoutput>
	</cfcase>
	<cfcase value="end">
		<cfoutput>

			<cfset horizontalTableClass = "c-#createUniqueId()#" />
			<cfset verticalTableClass = "c-#createUniqueId()#" />

			<core:ResponsiveStyles>
				@media only screen and ( max-width: 650px ) {

					.#horizontalTableClass# {
						display: none !important ;
					}

					.#verticalTableClass# {
						display: block !important ;
					}

				}
			</core:ResponsiveStyles>

			<core:Margin size="#attributes.margin.listFirst( ' ' )#" />

			<!---
				CAUTION: Display:None only seems to be supported in Outlook when applied
				to a TABLE. It does not seem to work on other elements.
			--->

			<!--- HORIZONTAL layout (rendered by default). --->
			<table width="100%" border="0" cellspacing="0" cellpadding="10" class="#horizontalTableClass#">
			<cfloop index="i" from="1" to="#images.len()#" step="2">
				<tr>
					<td width="50%" align="center">
						<img
							src="#images[ i ].src#"
							alt="#encodeForHtmlAttribute( images[ i ].title )#"
							width="244"
							height="124"
						/>
					</td>
					<td width="50%" align="center">
						<img
							src="#images[ i + 1 ].src#"
							alt="#encodeForHtmlAttribute( images[ i + 1 ].title )#"
							width="244"
							height="124"
						/>
					</td>
				</tr>
			</cfloop>
			</table>

			<!--- VERTICAL layout (hidden by default). --->
			<table width="100%" border="0" cellspacing="0" cellpadding="10" class="#verticalTableClass#" style="display: none ;">
			<cfloop value="image" array="#images#">
				<tr>
					<td>
						<img
							src="#image.src#"
							alt="#encodeForHtmlAttribute( image.title )#"
							style="width: 100% ;"
						/>
					</td>
				</tr>
			</cfloop>
			</table>

			<core:Margin size="#attributes.margin.listLast( ' ' )#" />

			<!--- Reset the generated content since we're overriding the output. --->
			<cfset thistag.generatedContent = "" />

		</cfoutput>
	</cfcase>
</cfswitch>

This code is dirty so it's not necessarily obvious what is happening. If you look closely, what you'll see is that I haven't actually created a responsive grid at all. Instead, what I've done is created two separate grids: one with a horizontal layout and one with a vertical layout. And, I'm swapping the two grids using a CSS media query.

The grid with the horizontal layout is the one that is shown by default; and, is the only one that older email clients will ever see. Then, the more modern email clients - the ones that have support for CSS media queries - will be able to progressively enhance to the vertical-layout grid if their user's window-width drops below 650px.

Oh, and of course display: none doesn't work on <div> tags in Microsoft Outlook, only <table> tags.

Are you beginning to see why HTML-based emails are a cozy little nightmare?

It's this very nightmare that I want to abstract away behind a series of ColdFusion custom tags. And so far, I'm kind of excited about the results. Here's an example that I've been building over the last few days - it imports several different directories of ColdFusion custom tags and assigns them each a prefix:

  • core: - These are the tags that all emails will use to wire everything together. Ultimately, the core tags output the HTML that goes into the email.

  • html: - These are the tags that represent native HTML elements. Only, under the hood, they are being constructed with inline CSS and being progressively enhanced with CSS media queries.

  • standard: - These are the tags that relate to a "standard" email design. They build on top of the core tags, but cater to a specific look-and-feel.

  • imageGrid: - These are the tags that are used to create the responsive image grid.

This is all still a work in progress; but, I really like where its headed:

<!--- Import custom tag libraries. --->
<cfimport prefix="core" taglib="./core/" />
<cfimport prefix="html" taglib="./core/html/" />
<cfimport prefix="imageGrid" taglib="./standard-layout/image-grid/" />
<cfimport prefix="standard" taglib="./standard-layout/" />

<!--- // ------------------------------------------------------------------------- // --->
<!--- // ------------------------------------------------------------------------- // --->

<core:email
	subject="Ben Nadel has met some amazing people!"
	teaser="See them for yourself!">

	<standard:Layout>

		<html:h1 margin="none xlarge">
			Ben Nadel has met some truly
			<html:a href="https://www.bennadel.com/people" decoration="false">
				amazing people
			</html:a>
		</html:h1>

		<html:p>
			Good morning my beautiful, beautiful friends!
		</html:p>

		<html:p>
			In the midst of the <html:em>stress</html:em> of this global pandemic, it's
			important to <html:strong>remain thankful</html:strong> for all the good
			people that we have in our lives. And, while we can't see our people as much
			as we might like, we can let the memory of them keep us warm.
		</html:p>

		<html:p>
			Here are some of the great people that I've met:
		</html:p>

		<imageGrid:List>
			<imageGrid:Image
				title="Sara Dunnack"
				src="https://bennadel-cdn.com/images/header/photos/sara_dunnack_3.jpg"
			/>
			<imageGrid:Image
				title="Sara Dunnack"
				src="https://bennadel-cdn.com/images/header/photos/sara_dunnack_3.jpg"
			/>
			<imageGrid:Image
				title="Sara Dunnack"
				src="https://bennadel-cdn.com/images/header/photos/sara_dunnack_3.jpg"
			/>
			<imageGrid:Image
				title="Sara Dunnack"
				src="https://bennadel-cdn.com/images/header/photos/sara_dunnack_3.jpg"
			/>
		</imageGrid:List>

		<html:p>
			Oh the memories! To see more wonderful people, check out my People section.
		</html:p>

		<standard:CallToAction href="https://www.bennadel.com/people" margin="large large">
			See More People
		</standard:CallToAction>

		<html:hr margin="large xlarge" />

		<html:p>
			The following is just a bunch of random style stuff to experiment with and
			to see how this all renders in email clients.
		</html:p>

		<html:h1>
			Favorite movies
		</html:h1>

		<html:p>
			There are a lot of great movies out there. I like movies across all genres;
			from action, to rom-com, to sci-fi, to drama &mdash; they all have their
			place. But, I have to admit that my <html:em>favorite genres</html:em> are
			definitely <html:strong>Action</html:strong> and
			<html:strong>Romantic Comedies</html:strong>
		</html:p>

		<html:h2>
			Best Meg Ryan movies
		</html:h2>

		<html:p>
			And ... when it comes to Romantic Comedies, there's no one better than Meg
			Ryan. For Years, Ryan was America's sweetheart, delighting audiences and 
			making us swoon in such films as:
		</html:p>

		<html:ul>
			<html:li>
				<html:a href="https://www.imdb.com/title/tt0098635/">
					When Harry Met Sally
				</html:a>
				&mdash;
				<html:mark>this is my favorite!</html:mark>
			</html:li>
			<html:li>
				<html:a href="https://www.imdb.com/title/tt0128853/">
					You've Got Mail
				</html:a>
			</html:li>
			<html:li>
				<html:a href="https://www.imdb.com/title/tt0108160/">
					Sleepless in Seattle
				</html:a>
			</html:li>
			<html:li>
				<html:a href="https://www.imdb.com/title/tt0113117/">
					French Kiss
				</html:a>
			</html:li>
		</html:ul>

		<html:h2>
			Great movie quotes
		</html:h2>

		<html:p>
			One of the great features of a <html:em>feature</html:em> film (see what I
			did there) is that they leave you with dynamite quotes that really make you
			think. One of the highly quotable scenes that I've been thinking about a lot
			lately is from Blade Runner.
		</html:p>

		<html:h3>
			Blade Runner (1982)
		</html:h3>

		<html:p>
			In the last scene of this sci-fi classic, Rutger Hauer contemplates existence
			in the final moments of this life:
		</html:p>

		<html:blockquote>
			<html:p margin="none">
				I've seen things you people wouldn't believe. Attack ships on fire off
				the shoulder of Orion. I watched C-beams glitter in the dark near the
				Tannhauser Gate. All those moments will be lost in time, like tears
				in rain.
			</html:p>
		</html:blockquote>

		<html:p>
			Now, in the <html:em>Director's cut</html:em> of the film, that the end of
			it. But, in the general release cut, Harris Ford follow-up with some
			narration that&mdash;in my opinion&mdash; adds a lot of needed color:
		</html:p>

		<html:blockquote>
			<html:p margin="none">
				I don't know why he saved my life. Maybe in those last moments he loved
				life more than he ever had before. Not just his life - anybody's life;
				my life. All he'd wanted were the same answers the rest of us want.
				Where did I come from? Where am I going? How long have I got? All I could
				do was sit there and watch him die.
			</html:p>
		</html:blockquote>

		<html:h2>
			Top 5 Arnold Schwarzenegger movies
		</html:h2>

		<html:p>
			Obviously, no discussion about movies could ever be considered complete if it
			didn't pay homage to the <html:em>master blaster</html:em> himself &mdash;
			<html:mark>Arnold Schwarzenegger</html:mark>. To make sure we cover all of
			our bases, here are my Top 5:
		</html:p>

		<html:ol>
			<html:li>
				<html:a href="https://www.imdb.com/title/tt0103064/">
					Terminator 2
				</html:a>
			</html:li>
			<html:li>
				<html:a href="https://www.imdb.com/title/tt0111503/">
					True Lies
				</html:a>
			</html:li>
			<html:li>
				<html:a href="https://www.imdb.com/title/tt0100802/">
					Total Recall
				</html:a>
			</html:li>
			<html:li>
				<html:a href="https://www.imdb.com/title/tt0099938/">
					Kindergarten Copy
				</html:a>
			</html:li>
			<html:li>
				<html:a href="https://www.imdb.com/title/tt0096320/">
					Twins
				</html:a>
			</html:li>
			<html:li>
				<html:a href="https://www.imdb.com/title/tt0093773/">
					Predator
				</html:a>
				&mdash;
				<html:mark>Because "5" isn't enough!</html:mark>
			</html:li>
		</html:ol>

	</standard:Layout>

	<core:TrackingPixel src="https://www.bennadel.com/tracking/pixel?foo=bar" />

</core:email>

As you can see, this is basically HTML. Only, it's not raw HTML - it's an abstraction of HTML using ColdFusion custom tags. Things like the Paragraph tag - <html:p> - live in the core html: prefix. But, more "specific" tags like the Call-To-Action tag - <standard:CallToAction> - live in the standard: prefix because they can't necessary be used in all the different types of emails.

Now, as we saw above, some of the ColdFusion custom tags - like the one for the response image grid - are fairly complex. Others, like the <html:mark> tag are more straightforward and just "wrap" the output in lower-level HTML:

<!--- Import custom tag libraries. --->
<cfimport prefix="core" taglib="../" />

<!--- // ------------------------------------------------------------------------- // --->
<!--- // ------------------------------------------------------------------------- // --->

<cfswitch expression="#thistag.executionMode#">
	<cfcase value="end">
		<cfoutput>

			<core:Styles variable="style">
				background-color: yellow ;
				display: inline-block ;
				font-weight: bold ;
				padding: 0px 4px 0px 4px ;
			</core:Styles>

			<mark style="#style#">#thistag.generatedContent#</mark>

			<!--- Reset the generated content since we're overriding the output. --->
			<cfset thistag.generatedContent = "" />

		</cfoutput>
	</cfcase>
</cfswitch>

This ColdFusion Custom Tag is just taking the child content of the tag - thistag.generatedContent - and is wrapping it in a native <mark> element that has an inline style tag. The inline styles are generated using the <core:Styles> tag which is, itself, a ColdFusion custom tag that deal with serializing and sorting the various properties:

<!--- Define custom tag attributes. --->
<cfparam name="attributes.variable" type="variableName" />

<!--- // ------------------------------------------------------------------------- // --->
<!--- // ------------------------------------------------------------------------- // --->

<cfswitch expression="#thistag.executionMode#">
	<cfcase value="end">

		<cfset caller[ attributes.variable ] = serializeProperties( thistag.generatedContent ) />

		<!--- This tag doesn't generate output - it only manipulates variable. --->
		<cfset thistag.generatedContent = "" />

	</cfcase>
</cfswitch>

<cfscript>
	
	public string function serializeProperties( required string content ) {

		var newline = chr( 10 );

		var style = content
			.reReplace( "(?m)^[ \t]+", "", "all" )
			.reReplace( "\r\n?|\n", newline, "all" )
			.listToArray( newline )
			.sort(
				( a, b ) => {

					// CAUTION: The "font-family" property is likely to be the longest
					// and is mostly likely to be WRAPPED by the SMTP server due to
					// line-length restrictions. As such, let's always sort the "font-
					// family" to the front of the resultant STYLE attribute so it has
					// the most possible breathing room.
					// --
					// NOTE: When the email is rendered, all STYLE attributes will be
					// placed on their own line, which should further help with wrapping
					// issues.
					if ( a.reFindNoCase( "^font-family" ) ) {

						return( -1 );

					} else if ( b.reFindNoCase( "^font-family" ) ) {

						return( 1 );

					}

					// CAUTION: For Outlook, the "mso-line-height-rule" is always
					// supposed to be defined before the "line-height" rule. As such,
					// let's bubble it up to the top (but after "font-family").
					if ( a.reFindNoCase( "^mso-line-height-rule" ) ) {

						return( -1 );

					} else if ( b.reFindNoCase( "^mso-line-height-rule" ) ) {

						return( 1 );

					}

					return( 0 );

				}
			)
			.toList( newline )
			.reReplace( "\s*;\s*", "; ", "all" )
			.reReplace( "\s*:\s*", ":", "all" )
			.reReplace( "\s*,\s*", ",", "all" )
			.reReplace( "\s+", " ", "all" )
			.trim()
		;

		return( style );

	}
	
</cfscript>

As you can see, this ColdFusion custom tag takes the content, serializes it, and then sets it back into the caller scope using the passed-in variable name. As it does this, it sorts the various CSS properties, paying special attention to font-family and mso-line-height-rule.

The font-family property is sorted to be the first property because it is likely to be the longest CSS property. Which means, it is the most likely to be split by the way the email client wraps lines. This is another part of the HTML-based email nightmare: some email clients arbitrarily wrap HTML content at 72-characters. And, if your inline style happens to get force-wrapped in the middle of a quoted font-family value then, guess what: your rendering will probably break in some weird way.

Again, I hope you're beginning to see why HTML-based emails are so challenging.

But, thanks to this budding abstraction of ColdFusion custom tags, if I test the above email using Litmus, here's what we get in a handful of different email clients:

AOL Mail Using Chrome

Apple Mail 12 in Dark Mode

GMail App on Android 8

GMail App on Android 7.1

GMail App Using Chrome

GMail App Using Explorer

GMail App Using Firefox

GMail App on iOS 13

IBM Notes 10 on Windows 10

iPhone 11

Outlook 2019 on Windows 10

Windows 10 Mail

As you can see, they're all a little different. They all a little broken in their own unique ways. But, for the most part, they all have a "good enough" experience. And, you can see that the image grid becomes "responsive" at a smaller size. So, it still works in Outlook 2019 using the horizontal layout; but, for modern mobile email clients - that support CSS media queries - we're able to optimize the experience for the narrow screen, switching over to the vertical layout.

Can you imagine testing all of this without Litmus? Holy chickens!

This is still a work in progress; but, I'm totally stoked on the results of this exploration so far. If you want to see the code, check out my GitHub repository - it has all the ColdFusion custom tags for this example. I have to say, I'm really enjoying being able to use native ColdFusion markup inside my ColdFusion application instead of having to jank together Handlebars and Node.js modules. My gut is telling me that this my future for email generation.

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

Reader Comments

15,674 Comments

@All,

On Twitter, Steve Rittler pointed me to a framework called MJML, which is a responsive framework for HTML-based emails provided by MailJet -- https://mjml.io/

It's pretty cool. They have a live editor so you can try out the various MJML components and then look at the compiled HTML to see how they are working their magic. For example, looking at their responsive multi-column layout, they only use a single Table, unlike me, where I used two tables. They make it work by creating a table inside MSO-comments (such as to target Outlook); but then, for non-MSO clients, they use inline-block to create the columns.

It's really very interesting stuff!

3 Comments

Interesting read @Ben. This looks like a great approach to take for email rendering. There is no-end of complexity and quirks to deal with and Litmus is certainly very useful.

I spent some time last year working on a CFML port of MJML. It ended up diverging in a few places and some of the HTML being output is slightly different, but the concepts are the same. Your post encouraged me to take a look again at where I got to with this and make a start on some documentation.

Only Lucee is tested/supported at the moment, but you might be interested to take a look:
https://github.com/cubiclabs/emml

There is also a simple live editor (not as fancy as the MJML one) giving a preview of the render and an example email as a starting point:
https://www.cubicstate.com/emml/

15,674 Comments

@Martin,

That's really cool! I'm very intrigued by the MJML style of email architecture. I spend quite a good amount of time looking through their demos last week and playing around with the editor. That was the first place I became familiar with the idea of a "Ghost Table" for wrapping MSO / Outlook content. Super clever! I really need to get my head wrapped around the responsive layouts a bit more.

I'll take some time to dig through your CFML port of the MJML concept. I'm curious to see what it looks like. I am sure there is some stuff I can borrow / steal from your concepts 😂 😂 😂 😂

Right now, I'm playing around with how something like my custom-tag approach can be themed. Hopefully I'll have a follow-up post for tomorrow. I think I'm onto something. Though, I worry that the performance may be problematic? I'm not sure. I mean, it's super fast for me, running locally. But, it has a lot of string-parsing. Which, may not be super fast under load.

Eh, I'll solve that problem later.

3 Comments

@Ben,

Yes - things like 'Ghost Tables' and GANGA (Gmail App for Non Google Accounts) are a whole new world and can make life very difficult. Fix something for one client and there is a chance you will have broken it in another!

Feel free to borrow / steal if you find something useful - I have done it from you for years!!

15,674 Comments

@All,

This morning, I added two new tags to help target content for Desktop vs. Mobile devices:

  • <core:IfDesktopView> ... desktop content ... </core:IfDesktopView>
  • <core:IfMobileView> ... mobile content ... </core:IfMobileView>

www.bennadel.com/blog/3981-using-coldfusion-custom-tags-to-create-an-html-email-dsl-in-lucee-cfml-5-3-7-47-part-iii.htm

It's not as elegant as some of the other solutions that people offer for truly responsive content. But, my brain is not so good as responsive designs, especially as designs get more complex. As such, brute-force showing X for Desktop and Y for Mobile just works best for my skill-set.

15,674 Comments

@All,

This morning, I added three new abstractions around CSS media queries:

www.bennadel.com/blog/3982-using-coldfusion-custom-tags-to-create-an-html-email-dsl-in-lucee-cfml-5-3-7-47-part-iv.htm

Basically, I'm tired of having to remember the @media syntax; and, perhaps even more important, having to remember to inject the !important property after every CSS rule. But, no more! Now, my <core:MaxWidthStyles> and <core:MinWidthStyles> take care of this for me!

15,674 Comments

@All,

This morning, I want to see if I could build on top of yesterday's CSS media query foundation, this time adding some Dark mode support:

www.bennadel.com/blog/3983-using-coldfusion-custom-tags-to-create-an-html-email-dsl-in-lucee-cfml-5-3-7-47-part-v.htm

It uses the same exactly approach as <core:MaxWidthStyles> - it's just a thin wrapper around the underlying MediaQueryStyles tag. Plus, I had to inject some additional information into the <head> tag to help email clients know that different color schemes are supported.

15,674 Comments

@All,

As I've been building on this concept, I keep thinking about how to strike a nice balance between complexity, simplicity, and flexibility. Ultimately, what I want is for HTML semantics to bubble up to the top while intricate layout logic fades into the background. Of course, when you have a complex layout, you need to put that markup somewhere.

To do this, I'm going to steal a concept from Angular: Slots. ColdFusion custom tags already have content projection. But, the concept of Slots brings another level of flexibility by providing more granular control over how content projection works:

www.bennadel.com/blog/3984-using-coldfusion-custom-tags-to-create-an-html-email-dsl-in-lucee-cfml-5-3-7-47-part-vi.htm

In this follow-up demo, I recreate the idea of a responsive image grid using multi Slot content projection.

15,674 Comments

@All,

Since I've been neck-deep in implementation details of this HTML email DSL for ColdFusion, I wanted to step back briefly and do a quick recap of the layout encapsulation techniques that we now have at our disposal:

www.bennadel.com/blog/3985-using-coldfusion-custom-tags-to-create-an-html-email-dsl-in-lucee-cfml-5-3-7-47-part-vii.htm

This includes:

  • The oldie, but goodie, <cfinclude>.
  • Custom tag attributes.
  • Custom tag generated content.
  • Multi-slot content projection (specific to the DSL).

This really gives us some amazing flexibility. And, an ability to keep the high-level markup simple while still implementing very complex layouts.

15,674 Comments

@All,

This morning I took a stab at adding <pre> and <code> wrappers:

www.bennadel.com/blog/3988-using-coldfusion-custom-tags-to-create-an-html-email-dsl-in-lucee-cfml-5-3-7-47-part-ix.htm

The pre tag poses some interesting challenges because it defers control over whitespace. This is at odds with the fact that my cf_email base tag removes all superfluous whitespace at the end of the rendering. In order to get the two concepts to play nicely together, I had to update the way the pre-tag content gets interpolated into the final output.

15,674 Comments

@All,

As I'm starting to apply this DSL concept to real world scenarios, I'm realizing that I need a better mechanism for making data available deep into the custom tag "DOM". For this, I'm borrowing the concept of Providers from Angular:

www.bennadel.com/blog/3992-using-coldfusion-custom-tags-to-create-an-html-email-dsl-in-lucee-cfml-5-3-7-47-part-xi.htm

This just allows high-level context to define key-value pairs that can then be accessed at a lower-level ColdFusion custom tag.

15,674 Comments

@All,

For my InVision hackathon project, I am trying to apply this DSL to the transactional emails as work. And, when I do work-work, I do it inside Docker for Mac. Moving into this new context surfaced a performance issue that I had not seen inside CommandBox:

www.bennadel.com/blog/3994-coldfusion-custom-tag-performance-differences-between-cfmodule-and-cfimport-in-lucee-cfml-5-3-7-47.htm

Docker is great because it brings uniformity to the development setup. However, Docker for Mac is notoriously bad at File-I/O. And, as it turns out, using the <CFImport> tag to invoke ColdFusion custom tags has significant file IO operations (when compared to <CFModule>).

15,674 Comments

@All,

As I've been putting this all together, I've been struggling to wrap my head around the best approach to font-weight, especially since a custom web font can have a huge variety; but, not all email clients support custom web fonts:

www.bennadel.com/blog/4002-using-coldfusion-custom-tags-to-create-an-html-email-dsl-in-lucee-cfml-5-3-7-47-part-xiii.htm

I think I have an approach that works: using <span> for font-weight 500 and then <strong> for 600, 700, and 800. This gives us better control over how things "fall back" in various clients.

15,674 Comments

@All,

A fast-follow on that last comment, I've moved the fix for Yahoo! Mail href attributes into my ColdFusion custom tag DSL:

www.bennadel.com/blog/4123-fixing-protocols-in-my-coldfusion-custom-tag-dsl-for-html-emails.htm

I decided to actually put the fix at a low-level, directly within the <html:a> and <html:img> tags. This way, the fix will just automatically happen when the developer updates their tags - and, it will work whether or not the passed-in attribute is encoded. It seems like the right approach.

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