Skip to main content
Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.

Using SMTP Headers To Send Custom MetaData Through Postmark In Lucee CFML 5.3.6.61

By Ben Nadel on
Tags: ColdFusion

The other day, I took a look at using CFMailParam / custom SMTP headers to include debug data in outbound emails in ColdFusion. In the comments to that post, Aaron Terry mentioned that my approach was fine from a "generic" standpoint; but, that most of the Email SaaS (Software as a Service) providers include special functionality for this very purpose. And, in fact, he pointed me to Postmark's metadata documentation. It turns out, if I include SMTP headers with the prefix, X-PM-Metadata-, Postmark will parse and remove those headers before the email reaches the end-user. This is an awesome feature! And, one I wanted to try for myself in Lucee CFML 5.3.6.61.

In my previous post, I was simulating a user-submitted comment in InVision; and, I was included context-specific SMTP headers that could help debug comment delivery and/or functionality issues:

  • X-InVision-From-User
  • X-InVision-To-User
  • X-InVision-CommentThread-Id
  • X-InVision-Comment-Id

In this post, I'm going to use the exact same code; however, instead of using the X-InVision- prefix in my SMTP headers, I'm going to use the X-PM-Metadata- prefix. This will tell Postmark to take those headers, show them in the Postmark App UI (User Interface), but strip them out of the email payload before the email is sent to the end user.

According to the Postmark documentation, there are a few key constraints for how the metadata fields work:

  • You can include up to 10 metadata fields in a single Postmark email message.

  • Metadata field names are limited to 20-characters (following the X-PM-Metadata- prefix I assume).

  • Metadata field values are limited to 80-characters.

  • Metadata field values are always stored and represented as Strings.

With that said, let's look at the revamped ColdFusion code:

<cfscript>

	// For the purposes of this demo, let's imagine that one user is posting a COMMENT.
	// And, that such an action triggers an email that gets sent to another user on the
	// same team. Here's the mock data for this workflow:

	// The user leaving the comment.
	authenticatedUser = {
		id: 1,
		name: "Ben Nadel",
		email: "ben@bennadel.com"
	};

	// The user who is meant to receive the comment notification.
	targetUser = {
		id: 4,
		name: "Sally Smith",
		email: "ben+sally@bennadel.com"
	};

	// The comment data itself.
	comment = {
		commentThreadID: 1001,
		commentID: 2001,
		content: "Hello Sally, how's it going?"
	};

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

	mail
		to = targetUser.email
		from = authenticatedUser.email
		subject = "#authenticatedUser.name# has left a comment for you"
		type = "html"

		// Don't spool the email, I want to see immediately if it fails.
		async = false

		// NOTE: Normally this would be defined as an Application setting or in the Lucee
		// server admin; but, for the sake of simplicity, I am providing these inline.
		server = "smtp.postmarkapp.com"
		port = "2525"
		username = request.postmarkApiToken
		password = request.postmarkApiToken
		{

		// In addition to the standard email content, we're also going to include some
		// reference data so that our Support / Engineering team can more easily see the
		// context in which this email was sent, should they need to debug anything.
		// --
		// NOTE: By prefixing the SMTP Headers with "X-PM-Metadata-", they will be
		// parsed and included in the Postmark App UI as "meta data", but they will be
		// STRIPPED OUT of the email before the email is sent to the target user. As
		// such, this meta data will only be visible to our internal team, not to our
		// customers.
		mailparam
			name = "X-PM-Metadata-From-User"
			value = authenticatedUser.id
		;
		mailparam
			name = "X-PM-Metadata-To-User"
			value = targetUser.id
		;
		mailparam
			name = "X-PM-Metadata-CommentThread-Id"
			value = comment.commentThreadID
		;
		mailparam
			name = "X-PM-Metadata-Comment-Id"
			value = comment.commentID
		;

		```
		<cfoutput>
			<h1>
				#encodeForHtml( authenticatedUser.name )# has left a comment
			</h1>

			<p>
				#encodeForHtml( comment.content )#
			</p>
		</cfoutput>
		```

	}

</cfscript>

As you can see, I'm using the CFMailParam tag to include custom SMTP headers that start with the X-PM-Metadata- prefix. Now, when we send this email out using the CFMail tag and look in the Postmark App UI, we see the following:

Custom metadata headers shown clearly in the Postmark App UI.

As you can see, our custom SMTP headers were parsed and displayed very neatly in the "Metadata" portion of Postmark's App UI. How awesome is that! And, as a special bonus, none of those SMTP headers reach the end-user. If I look at the raw source of the email that ends-up going to Sally, none of these headers are present.

A huge shout-out to Aaron Terry for pointed me in this direction. It seems that in the 8+ years I've been a Postmark customer, I've been negligent in following up on all of their subsequent feature releases. It's time for me to look through their documentation and see what other bits of magic I might be able to leverage at InVision in Lucee CFML.



Reader Comments

Now this is a much cleaner approach. I prefer that the end user doesn't see our debugging data, even though most users won't have a clue what it means:)

The only issue, is that I will need to pay for yet another 3rd Party API Service.
I am already using SparkPost, so I will see, if they have the same feature set. It is quite likely that they will. As most of these mail services are in fierce competition with each other.

Thank you for providing yet another informative tutorial.

Reply to this Comment

@Charles,

Yeah, agreed - this feels much cleaner. Glad to hear that SparkPost has a similar thing.

Reply to this Comment

Wohow, I love PostmarkApp as well and this adds so much usefulness to track down important emails. Had never even read/realised this could be done. Your code comments are, as always, super helpful too!

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Blog
Live in the Now
Oops!
NEW: Some basic markdown formatting is now supported: bold, italic, blockquotes, lists, fenced code-blocks. Read more about markdown syntax »
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.