Exit
  • Global community
    • Language:
      • Deutsch
      • English
      • Español
      • Français
      • Português
  • 日本語コミュニティ
  • 한국 커뮤니티
0

JSON-Postmark trials

Participant ,
Mar 05, 2025 Mar 05, 2025

Recently, Postmark became less forgiving about the stuff people were sending them. I had been using some code 8 years ago from gitHub that prepared and sent email templates via Postmark. I've just dragged that code out again and it is erroring all over town right now.

 

I really hope I've provided enough detail and also hope someone is patient enough to trawl through it. In places throughout the code, I've added comments like "<!--- SEE OUTPUT #2 --->". These refer to various dumps I've added at the bottom to let you see the results of the processing.

 

I also ran some tests using simpler HTML and even plain text, the results of which I've also included at the bottom. Basically, everything I've tried results in the same Postmark error:

{""ErrorCode"":403,""Message"":""Invalid request field(s): $.HTMLBody""}

So, at this point I'm concluding that the formatting of the JSON for Postmark consumption is going wrong somewhere.

If you need any more information, please ask.

 

My processing goes as follows:

 

Step 1: User shows interest in a trade-related job and clicks "Chase Job". This calls a function that generates the appropriate email template, much of the content stored in a database, then sends it off to the Postmark API cfc.

<cffunction name="SendJobStatusEmail" access="remote" returnType="boolean">
   .....
   <cfswitch expression = "#arguments.status#">
      <cfcase value="2">
         <cfset vSubject = "Job Interest">
         <cfset vIntro = getTemplates(name="Intro",job_status=1,entity="users").data>
         <cfset vSecondaryText = getTemplates(name="SecondaryText",job_status=2).data>
         <cfset vButtonText = 'View Tradies'>
         <cfset vPostButtonText = ''>
         <cfset vButtonRedir = urlEncodedFormat(siteroot & vJobLink & '...)>
      </cfcase>
          ....
   </cfswitch>
     ...
   <cfsavecontent variable="emailBody">
      <cfoutput>#qTemplate.data#</cfoutput> <!--- SEE OUTPUT #1 --->
   </cfsavecontent>
     ....
   <cfset resPostmark = oPostmark.sendMail(
      mailTo = qJob.recipient_email
      ,mailFrom = application.fromEmail
      ,mailSubject = vSubject
      ,apiKey = application.postmarkAPIKey
      ,mailHTML = emailBody
   )>
   ....
</cfunction>

 

Step 2:  Postmark API receives these arguments and processes them as follows:

<cfcomponent name="PostMarkAPI">
   <cffunction name="sendMail" access="public" returntype="string" description="Create JSON packet -> Postmarkapp">
      <cfargument name="mailTo" required="yes" type="string"/>
      <cfargument name="mailFrom" required="yes" type="string"/>
      <cfargument name="mailSubject" required="yes" type="string"/>
      <cfargument name="apiKey" required="yes" type="string"/>
      <cfargument name="mailReply" required="no" type="string"/>
      <cfargument name="mailCc" required="no" type="string"/>
      <cfargument name="mailHTML" required="no" type="string" />
      <cfargument name="mailTxt" required="no" type="string"/>  
      <cfset var vApiURL = "http://api.postmarkapp.com/email" />
      <cfset var vmailto = JSStringFormat(arguments.mailTo) />
      <cfset var vmailFrom = JSStringFormat(arguments.mailFrom) />
      <cfset var vmailSubject = JSStringFormat(arguments.mailSubject) />
      <cfset var vmailCc = '' />
      <cfset var vmailHTML = '' />
      <cfset var vmailTxt = '' />
      <cfset var vmailReply = '' />
      <cfset vmailCc = JSStringFormat(arguments.mailCc) />
      <cfset vmailHTML = arguments.mailHTML.reReplaceNoCase(">\s",">","all").reReplaceNoCase("\s<","<","all")>
      <cfset vmailHTML = JSStringFormat(arguments.mailHTML) />
      <cfset vmailTxt = JSStringFormat(arguments.mailTxt) />
      <cfset vmailReply = JSStringFormat(arguments.mailReply) />
      <cfset vmailCc = arguments.mailCc />
      <cfset vmailTxt = arguments.mailTxt />
      <cfset vmailReply = arguments.mailReply />
      
      <cflog file="ace" text="#vmailHTML#"> <!--- SEE OUTPUT #2 --->
      
      <cfsavecontent variable="jsonPacket">
         <cfprocessingdirective suppressWhiteSpace="yes">
            <cfoutput>
            {
               "From" : "#vmailFrom#",
               "To" : "#vmailto#",
               <cfif len(trim(vmailCc))>"Cc" : "#vmailCc#",</cfif>
               "Subject" : "#vmailSubject#"
               <cfif len(trim(vmailHTML))>, "HTMLBody" : "#vmailHTML#"</cfif>
               <cfif len(trim(vmailTxt))>, "TextBody" : "#vmailTxt#"</cfif>
               <cfif len(trim(vmailReply))>, "ReplyTo" : "#vmailReply#"</cfif>
            }
            </cfoutput>
         </cfprocessingdirective> 
      </cfsavecontent> 
      <cflog file="ace" text="#jsonPacket#"> <!--- SEE OUTPUT #3 --->
     
      <cfhttp url="https://api.postmarkapp.com/email" redirect="yes" method="post" >
         <cfhttpparam type="header" name="Accept" value="application/json" />
         <cfhttpparam type="header" name="Content-type" value="application/json" />
         <cfhttpparam type="header" name="X-Postmark-Server-Token" value="#arguments.apiKey#" />
         <cfhttpparam type="body" encoded="no" value="#jsonPacket#" />
      </cfhttp>
      <cfreturn cfhttp.responseHeader.Status_Code />  
      <cflog file="aceoftradesErrors" text="#cfhttp.fileContent#"> <!--- SEE OUTPUT #4 --->
      ....
   </cffunction>
</cfcomponent>

If I run bare bones HTML through, such as the following

<cfsavecontent variable="emailBody">
   <html><div> <p>Hi there. How's it going?</p>  </div> </html>
</cfsavecontent>

 

It builds a jasonPacket:

{ ""From"" : ""info@vwxyz.co.nz"", ""To"" : ""info@vwxyz.co.nz"", ""Subject"" : ""Good news - you have interest in your job"" , ""HTMLBody"" : ""\r\n \r\n \r\n <html><div><p>Hi there. What\'s happening?</p></div></html>\n "" }

(Note: All the "\r\n" stuff comes from the JSStringFormat() in the PostMarkAPI cfc. It doesn't make any difference to the result if I remove that.)

 

and throws a cfhttp.fileContent error:

{""ErrorCode"":403,""Message"":""Invalid request field(s): $.HTMLBody""} 

 

If I run bare bones HTML through, such as the following

<cfsavecontent variable="emailBody">
<html><div> <p>Hi there. How's it going?</p> </div> </html>
</cfsavecontent>

or plain text

<cfsavecontent variable="emailBody">
   Hi there, what's the happs?
</cfsavecontent>

 

I get the same results

 

OUTPUT DUMPS

----------------------

1)

<div style=""margin:0em auto;padding:0em;width:600px;min-width:600px""><div style=""padding:1.25em 50px;text-align:center""><a href=""#application.siteroot#/?utm_campaign=transactional&utm_source=emails.logo&utm_medium=email""><img src=""http://aceoftrades/images/website/logo10-180.png""></a></div><div align=""center"" style=""margin:0em auto;padding:0em;background-color:##fff;width:600px;min-width:600px;border-radius:3px""><div style=""width:550px;min-width:550px;padding-top:0.5em;padding-bottom:1em""><div style=""font-size:15px;text-align:left;font-family:Arial,Helvetica,sans-serif;color:##666""><p style=""font-family:'Kameron';font-size:130%;padding-top:1.25em;padding-bottom:0em;margin:0;font-weight:bold"">Hi <a rel=""nofollow"" style=""text-decoration:none;color:##666"">#qJobDetails.recipient_name#</a>,</p><div style=""padding-bottom:1em""><p>#vIntro#</p><p>#vSecondaryText#</p><cfif len(trim(vButtonText))><div style=""text-align:center;padding:1.5em 0.5em""><a href=""#application.siteroot#/track_click.cfm?type=email&object=job_link&subject=job_alert&redir=#vJobLink#"">#vButtonText#</a></div></cfif><cfif len(trim(vSecondaryText))><p>#vSecondaryText#</p></cfif></div></div></div></div><div style=""padding:1.5em 50px 0 50px""><div style=""color:##999;font-size:85%;text-align:center"">Delivered to <span style=""text-decoration:none""><span style=""color:##555""><a href=""mailto:#qJobDetails.recipient_email#"" target=""_blank"">#qJobDetails.recipient_email#</a></span></span><br>&copy; #year(now())# #application.author# | <a href=""#application.siteroot#/contact?subject=bug"">report an issue</a></div></div></div> 

 

2) 

<div style=\""margin:0em auto;padding:0em;width:600px;min-width:600px\""><div style=\""padding:1.25em 50px;text-align:center\""><a href=\""#application.siteroot#/?utm_campaign=transactional&utm_source=emails.logo&utm_medium=email\""><img src=\""http://aceoftrades/images/website/logo10-180.png\""></a></div><div align=\""center\"" style=\""margin:0em auto;padding:0em;background-color:##fff;width:600px;min-width:600px;border-radius:3px\""><div style=\""width:550px;min-width:550px;padding-top:0.5em;padding-bottom:1em\""><div style=\""font-size:15px;text-align:left;font-family:Arial,Helvetica,sans-serif;color:##666\""><p style=\""font-family:\'Kameron\';font-size:130%;padding-top:1.25em;padding-bottom:0em;margin:0;font-weight:bold\"">Hi<a rel=\""nofollow\"" style=\""text-decoration:none;color:##666\"">#qJobDetails.recipient_name#</a>,</p><div style=\""padding-bottom:1em\""><p>#vIntro#</p><p>#vSecondaryText#</p><cfif len(trim(vButtonText))><div style=\""text-align:center;padding:1.5em 0.5em\""><a href=\""#application.siteroot#/track_click.cfm?type=email&object=job_link&subject=job_alert&redir=#vJobLink#\"">#vButtonText#</a></div></cfif><cfif len(trim(vSecondaryText))><p>#vSecondaryText#</p></cfif></div></div></div></div><div style=\""padding:1.5em 50px 0 50px\""><div style=\""color:##999;font-size:85%;text-align:center\"">Delivered to<span style=\""text-decoration:none\""><span style=\""color:##555\""><a href=\""mailto:#qJobDetails.recipient_email#\"" target=\""_blank\"">#qJobDetails.recipient_email#</a></span></span><br>&copy; #year(now())# #application.author# |<a href=\""#application.siteroot#/contact?subject=bug\"">report an issue</a></div></div></div>

3)  Without JSStringFormat:

{ ""From"" : ""info@vwxyz.co.nz"", ""To"" : ""info@vwxyz.co.nz"", ""Subject"" : ""Good news - you have interest in your job"" , ""HTMLBody"" : ""<div style=""margin:0em auto;padding:0em;width:600px;min-width:600px""><div style=""padding:1.25em 50px;text-align:center""><a href=""#application.siteroot#/?utm_campaign=transactional&utm_source=emails.logo&utm_medium=email""><img src=""http://aceoftrades/images/website/logo10-180.png""></a></div><div align=""center"" style=""margin:0em auto;padding:0em;background-color:##fff;width:600px;min-width:600px;border-radius:3px""><div style=""width:550px;min-width:550px;padding-top:0.5em;padding-bottom:1em""><div style=""font-size:15px;text-align:left;font-family:Arial,Helvetica,sans-serif;color:##666""><p style=""font-family:'Kameron';font-size:130%;padding-top:1.25em;padding-bottom:0em;margin:0;font-weight:bold"">Hi<a rel=""nofollow"" style=""text-decoration:none;color:##666"">#qJobDetails.recipient_name#</a>,</p><div style=""padding-bottom:1em""><p>#vIntro#</p><p>#vSecondaryText#</p><cfif len(trim(vButtonText))><div style=""text-align:center;padding:1.5em 0.5em""><a href=""#application.siteroot#/track_click.cfm?type=email&object=job_link&subject=job_alert&redir=#vJobLink#"">#vButtonText#</a></div></cfif><cfif len(trim(vSecondaryText))><p>#vSecondaryText#</p></cfif></div></div></div></div><div style=""padding:1.5em 50px 0 50px""><div style=""color:##999;font-size:85%;text-align:center"">Delivered to<span style=""text-decoration:none""><span style=""color:##555""><a href=""mailto:#qJobDetails.recipient_email#"" target=""_blank"">#qJobDetails.recipient_email#</a></span></span><br>&copy; #year(now())# #application.author# |<a href=""#application.siteroot#/contact?subject=bug"">report an issue</a></div></div></div>"" }

4)

cfhttp.fileContent: {""ErrorCode"":403,""Message"":""Invalid request field(s): $.HTMLBody""} 

 

jsonlint error:

JSONLint Error
Error: Parse error on line 1:
{ ""From"" : ""info@vwxy
----^

 

717
Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines

correct answers 1 Correct answer

Participant , Mar 06, 2025 Mar 06, 2025

OK, I think I've got it. It doesn't like quotes, e.g. in the following

<div style="text-align:center">

 So, I ran a replace() on the html

<cfset emailBody = emailBody.replace(chr(34), "&quot;", "all")>

and it got through. So woohoo! That only took days..

Translate
Community Expert ,
Mar 05, 2025 Mar 05, 2025

I've not worked with pm, but one thing to consider is that the docs indicate the case to be HtmlBody rather than your HTMLBody. It's worth a shot to change that. The docs mention recent tightening of the api.

 

Also, have you tried your vmailTxt variant? Besides the docs indicating how that's more forgiving than htmlbody, note also your code has its case correct (per the docs), as TextBody. 


/Charlie (troubleshooter, carehart. org)
Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Participant ,
Mar 06, 2025 Mar 06, 2025

Hi Charlie, I tried changing the case to HtmlBody. It was worth a shot, but didn't do the job.

The only thing I've managed to get to pass through so far is the following:

 

<cfset vmailHTML = "<html>\n<body>\n<div>This is valid</div>\n</body>\n</html>">

 

This is in the postmark docs where they describe their recent changes and apparently relates to HtmlBody with "newlines".

 

Valid HtmlBody with newlines

{
 "HtmlBody": "<html>\n<body>\n<div>This is valid</div>\n</body>\n</html>"
}

 

So, I thought I'd just wrap all my tag groups with \n.

Somebody gave me a regex on codecademy.com

 

var newString = oldString.replace(/></g, ">\n<")

 

I adapted it to CF with

<cfset vmailHTML = "<html><body><div>This is valid</div></body></html>">
<cfset vmailHTML = REReplace(vmailHTML,"/></g", ">\n<",'ALL')>

This only works if there is no whitespace between HTML tags, i.e.

<html>  <body> <div>This is valid</div>   </body> </html>

So, it needs a whitespace cleanse before putting it through the regex. Or just write your HTML nice and tight and minified.

It works, but I feel like I'm working around something that is probably straightforward.

 

 

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Mar 06, 2025 Mar 06, 2025

Hi @paul_8809 , problem solved?

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Participant ,
Mar 06, 2025 Mar 06, 2025

No, lol. That regex doesn't work as I thought and I don't think it's necessary. Very simple html passes through without adding "\n" to anything as long as it's minified - no whitespace etc. But, as soon as I try to get my complex html through, it complains. So, it must be something in that HTML. 

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Participant ,
Mar 06, 2025 Mar 06, 2025

OK, I think I've got it. It doesn't like quotes, e.g. in the following

<div style="text-align:center">

 So, I ran a replace() on the html

<cfset emailBody = emailBody.replace(chr(34), "&quot;", "all")>

and it got through. So woohoo! That only took days..

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Community Expert ,
Mar 06, 2025 Mar 06, 2025

Oh, glad to hear. Thanks for sharing the good news. Thanks also for sharing your experience. It will certainly help someone else in future. 

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Participant ,
Mar 06, 2025 Mar 06, 2025
LATEST

A downstream issue has arisen that I can't seem to solve. I'll write a new question though.

Translate
Report
Community guidelines
Be kind and respectful, give credit to the original source of content, and search for duplicates before posting. Learn more
community guidelines
Resources