Skip to main content
Ben Nadel at InVision In Real Life (IRL) 2018 (Hollywood, CA) with: Johnathan Hunt
Ben Nadel at InVision In Real Life (IRL) 2018 (Hollywood, CA) with: Johnathan Hunt

Exploring Postmark Tags For Grouping Related Transactional Emails In Lucee CFML 5.3.6.61

By
Published in Comments (2)

At InVision, we use Postmark to send both our outbound transactional emails and handle inbound reply emails. We've been using them for over 8-years and it's been just a seamless, outstanding experience. In those 8-years, however, Postmark has added a number of features that we have yet to take advantage of. The other day, I looked at injecting debugging meta-data using SMTP headers. Today, I wanted to look at using Tags to group our transaction emails using the CFMail and CFMailParam tags in Lucee CFML 5.3.6.61.

Just as with the SMTP meta-data, a Tag is defined using CFMailParam and an SMTP header: X-PM-Tag. I couldn't seem to find much information about the limitations of the tag value, such as how long it's allowed to be. However, only one tag can be provided per transactional email.

To explore the use of Tags - and how they manifest themselves in the Postmark application UI (User Interface) - I created a dummy ColdFusion component to send out some sample emails. This ColdFusion component contains three methods to send out three emails:

  • sendDailyDigestEmail()
  • sendPasswordResetEmail()
  • sendWelcomeEmail()

And, each of these emails is going to contain a unique X-PM-Tag SMTP header, respectively:

  • X-PM-Tag = DailyDigestEmail
  • X-PM-Tag = PasswordResetEmail
  • X-PM-Tag = WelcomeEmail

Here's the code - remember, these are just dummy emails, don't pay attention to the actual email body (even though it uses the amazing tag islands feature available in Lucee CFML):

component
	output = false
	hint = "I provide methods for sending out transactional system emails."
	{

	/**
	* I initialize the emailer with the given SMTP settings.
	* 
	* @smtpServer I am the SMTP server address.
	* @smtpPort I am the SMTP server port.
	* @postmarkApiToken I am the Postmark API token used to authenticate emails.
	*/
	public void function init(
		required string smtpServer,
		required string smtpPort,
		required string postmarkApiToken
		) {

		variables.smtpServer = arguments.smtpServer;
		variables.smtpPort = arguments.smtpPort;
		variables.postmarkApiToken = arguments.postmarkApiToken;

		// Postmark can only send from a predefined set of signatures.
		variables.systemEmail = "ben@bennadel.com";

		// All outbound CFMail tags will share this set of base attributes.
		variables.baseMailAttributes = {
			from: systemEmail,
			type: "html",
			// For the sake of the demo, I don't want to spool emails, I want to see
			// errors immediately in the response if they fail.
			async: false,
			server: smtpServer,
			port: smtpPort,
			username: postmarkApiToken,
			password: postmarkApiToken
		};

	}

	// ---
	// PUBLIC METHODS.
	// ---

	/**
	* I send the user a digest of recent activity related to their account.
	* 
	* @toEmail I am the user to which the email is being sent.
	* @digestActivityItems I am the collection of activity items to report.
	*/
	public void function sendDailyDigestEmail(
		required string toEmail,
		required array digestActivityItems
		) {

		mail
			to = toEmail
			subject = "Your InVision daily digest"
			attributeCollection = baseMailAttributes
			{

			mailparam
				name = "X-PM-Tag"
				value = "DailyDigestEmail"
			;

			```
			<cfoutput>
				<h1>
					You have been a busy bee!
				</h1>
				<p>
					Here are you #encodeForHtml( digestActivityItems.len() )# items: ...
				</p>
			</cfoutput>
			```
		}

	}


	/**
	* I send the user an email that contains a password-reset call-to-action (CTA).
	* 
	* @toEmail I am the user to which the email is being sent.
	* @passwordResetUrl I am the password reset URL (CTA).
	*/
	public void function sendPasswordResetEmail(
		required string toEmail,
		required string passwordResetUrl
		) {

		mail
			to = toEmail
			subject = "Reset your InVision password"
			attributeCollection = baseMailAttributes
			{

			mailparam
				name = "X-PM-Tag"
				value = "PasswordResetEmail"
			;

			```
			<cfoutput>
				<h1>
					Please use the following link to reset your InVision password
				</h1>
				<p>
					<a href="#encodeForHtmlAttribute( passwordResetUrl )#">Reset password</a>
				</p>
			</cfoutput>
			```
		}

	}


	/**
	* I send the user the InVision welcome email.
	* 
	* @toEmail I am the user to which the email is being sent.
	*/
	public void function sendWelcomeEmail( required string toEmail ) {

		mail
			to = toEmail
			subject = "Welcome to InVision!"
			attributeCollection = baseMailAttributes
			{

			mailparam
				name = "X-PM-Tag"
				value = "WelcomeEmail"
			;

			```
			<cfoutput>
				<h1>
					Welcome to InVision!
				</h1>
				<p>
					This is going to be awesome. Are you ready to begin?
				</p>
			</cfoutput>
			```
		}

	}

}

As you can see, each email contains a CFMailParam tag through which we are defining an SMTP header. This SMTP header contains our Postmark tag, which is then incorporated into the Activity stream within the Postmark UI.

ASIDE: Unlike with the Postmark meta-data headers, the X-PM-Tag SMTP header is not stripped out of the outbound email. As such, these tag values can be seen in the source within the user's email client. Don't use tags to pass around private information - use the X-PM-Metadata-* headers for private information.

Now, to try sending out a few of these emails:

<cfscript>

	emailer = new Emailer(
		smtpServer = "smtp.postmarkapp.com",
		smtpPort = "2525",
		postmarkApiToken = request.postmarkApiToken
	);

	// Send out test emails with embedded Postmark Tags.
	emailer.sendWelcomeEmail( getRandomEmail() );
	emailer.sendPasswordResetEmail( getRandomEmail(), "https://reset.my.password" );
	emailer.sendDailyDigestEmail( getRandomEmail(), [ {}, {}, {} ] );

	// ------------------------------------------------------------------------------- //
	// ------------------------------------------------------------------------------- //

	/**
	* I return a random test email to send-to.
	*/
	public string function getRandomEmail() {

		var emails = [
			"ben+doria@bennadel.com", "ben+timmy@bennadel.com", "ben+steve@bennadel.com",
			"ben+jojo@bennadel.com", "ben+sonia@bennadel.com", "ben+clay@bennadel.com",
			"ben+anna@bennadel.com", "ben+roland@bennadel.com", "ben+dave@bennadel.com"
		];

		return( emails[ randRange( 1, emails.len() ) ] );

	}

</cfscript>

Once we send out some ColdFusion emails using CFMail and CFMailParam with out SMTP headers, if we pop over to the Postmark application console, we can see our Tags start to show up in the activity stream:

And, what's even cooler is that we can then filter by tags by either clicking on one of the tags in the activity stream; or, by selecting a tag from the Filter drop-down menu:

The Postmark activity stream filter UI allows filtering by multiple tags.

Seeing this functionality in action, I feel silly saying that, historically, I've had to find groups of emails by searching based on Subject line value. Which is to say, a "fuzzy match" at best, especially since our Subject lines tend to be dynamic based on user. This Tag functionality is really going to make finding a particular email much easier for our Product and Support Engineers!

For 8-years, I've been extremely happy with Postmark as both an outbound and inbound email SaaS (Software as a Service) provider. And now, with things like custom meta-data headers and Tagging, things are only looking better! Plus, I love how easy these features are to leverage using their SMTP API within Lucee CFML 5.3.6.61.

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

Reader Comments

4 Comments

But why continue to use the SMTP API instead of the REST API? We were delighted with our move to the Postmark API with email templates; it just made transaction emails a lot easier all round.

15,880 Comments

@Geoff,

Purely historical momentum. We have a lot of code that uses the SMTP approach. That said, it is all mostly encapsulated in a ColdFusion component (EmailService.cfc); as such, it would be fairly straightforward to start converting <cfmail> tags to HTTP calls. At the end of the day, though, I am not sure that we're actually feeling any "pain points" around the SMTP approach.

I will admit, though, I didn't even know Postmark_ had email templates! I swear, they quietly snuck all of this stuff in there over the last 8-years and I was completely oblivious to it :D I'll take a look at that stuff, it sounds cool. I've been "hand crafting" emails for the last decade and it never gets any easier.

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