Using SMTP Headers To Send Custom MetaData Through Postmark In Lucee CFML 5.3.6.61
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:
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.
Want to use code from this post? Check out the license.
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.
On sparkpost, it's the metadata struct within X-MSYS-API header.
https://developers.sparkpost.com/api/smtp/#header-using-the-x-msys-api-custom-header
Like Sendgrid's X-SMTP-API header .... you embed a JSON struct in that header that controls a slew of options and features, with custom metadata being one of them.
@Aaron Terry
Thank you for the advice.
I will have a look for this feature, the next time, I log into my SparkPost dashboard.
@Charles,
Yeah, agreed - this feels much cleaner. Glad to hear that SparkPost has a similar thing.
@All,
A quick related follow-up post on using Tags in Postmark:
www.bennadel.com/blog/3908-exploring-postmark-tags-for-grouping-related-transactional-emails-in-lucee-cfml-5-3-6-61.htm
Tags, like Metadata, uses a custom SMTP header,
X-PM-Tag
, and allows us to both view and filter Postmark's activity stream based on Tag values. This is gonna be awesome!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!
@Jonas,
Awesome, so glad you found this helpful :D