PageRenderTime 75ms CodeModel.GetById 31ms RepoModel.GetById 0ms app.codeStats 0ms

/system/core/mail/protocols/PostmarkProtocol.cfc

https://github.com/mmckellip/coldbox-platform
ColdFusion CFScript | 243 lines | 192 code | 49 blank | 2 comment | 23 complexity | 128d35d431b5bcac10a6bc9020f28d13 MD5 | raw file
  1. <!-----------------------------------------------------------------------
  2. ********************************************************************************
  3. Copyright Since 2005 ColdBox Framework by Luis Majano and Ortus Solutions, Corp
  4. www.coldbox.org | www.luismajano.com | www.ortussolutions.com
  5. ********************************************************************************
  6. Author : Luis Majano & Robert Rawlings
  7. Description :
  8. A mail protocol that sends via http://postmarkapp.com/
  9. ----------------------------------------------------------------------->
  10. <cfcomponent extends="coldbox.system.core.mail.abstractprotocol" output="false" hint="A mail protocol that sends via http://postmarkapp.com/">
  11. <!--- init --->
  12. <cffunction name="init" access="public" returntype="PostmarkProtocol" hint="Constructor" output="false">
  13. <cfargument name="properties" required="false" default="#structnew()#" hint="A map of configuration properties for the protocol" />
  14. <cfscript>
  15. super.init(argumentCollection=arguments);
  16. // Property Checks
  17. if(NOT propertyExists("APIKey")){
  18. // No API key was found, so throw an exception.
  19. throw(message="ApiKey is Required",type="PostmarkProtocol.PropertyNotFound");
  20. }
  21. return this;
  22. </cfscript>
  23. </cffunction>
  24. <!------------------------------------------- PUBLIC ------------------------------------------>
  25. <cffunction name="send" access="public" returntype="struct" output="true" hint="Send an email payload. Returns a struct: [error:boolean,errorArray:array,messageid:string]">
  26. <cfargument name="mail" required="true" type="coldbox.system.core.mail.Mail" hint="The mail payload to send." />
  27. <!--- Create a temporary local structure. --->
  28. <cfset var local = structNew() />
  29. <!--- Create a default return structure. --->
  30. <cfset local.rtnStruct = structNew() />
  31. <cfset local.rtnStruct.error = true />
  32. <cfset local.rtnStruct.errorArray = arrayNew(1) />
  33. <!--- If we've made it this far then then the payload should be fit for purpose. --->
  34. <!--- Get the information from the email payload. --->
  35. <cfset local.data = arguments.mail.getMemento() />
  36. <!--- We can now start to assemble our augmented payload ready to send to PostMark. --->
  37. <!--- Create a default array for the custom mail headers. --->
  38. <cfset local.headers = arrayNew(1) />
  39. <!--- And another one for the attachments. --->
  40. <cfset local.attachments = arrayNew(1) />
  41. <!--- Both custom headers and attachments are defined in the mail params for the coldbox mail payload. --->
  42. <!--- Loop over the mail params, we can then extract the headers and arrachments from it. --->
  43. <cfloop array="#mail.getMailParams()#" index="local.mailparam">
  44. <!--- Check that this header is a 'name' element. --->
  45. <cfif structKeyExists(local.mailparam, "name")>
  46. <!--- Append the encoded header structure into the array of headers. --->
  47. <cfset arrayAppend(local.headers, encodeHeader(local.mailparam)) />
  48. <!--- Now check to see if this mailparam is an attachment. --->
  49. <cfelseif structKeyExists(local.mailparam, "file")>
  50. <!--- Append the encoded attachment structure to the array. --->
  51. <cfset arrayAppend(LOCAL.Attachments, encodeAttachment(local.mailparam)) />
  52. </cfif>
  53. </cfloop>
  54. <!--- Check to see if the nasmed header array has any length. --->
  55. <cfif arrayLen(local.headers)>
  56. <!--- We have some custom named headers to add to the mail. --->
  57. <!--- Add them to the payload structure. --->
  58. <cfset local.data["Headers"] = local.headers />
  59. </cfif>
  60. <!--- Check to see if we have any attachements. --->
  61. <cfif arrayLen(local.attachments)>
  62. <!--- We have some attachments to add to the mail. --->
  63. <!--- Add them to the payload structure. --->
  64. <cfset local.data["Attachments"] = local.attachments />
  65. </cfif>
  66. <!--- Now we're going to render the body contend for the email. --->
  67. <!--- We'll start by looking at the standard mail body, rather than any mailparts. --->
  68. <!--- We start by assessing what type has been set for the content. --->
  69. <cfif structKeyExists(local.data, "type") AND local.data["type"] EQ "html">
  70. <!--- This is an html email, set the body as an html body. --->
  71. <!--- Ammend the keys so that PostMark can understand them. --->
  72. <!--- This is because PostMark doesn't use a key named Body, but one named HTMLBody --->
  73. <cfset local.data["HtmlBody"] = local.data["Body"] />
  74. <cfelse>
  75. <!--- This has no specific type of something other thank html set, so we'll assume it's a plain text body. --->
  76. <!--- Ammend the keys so that PostMark can understand them. --->
  77. <!--- This is because PostMark doesn't use a key named Body, but one named HTMLBody --->
  78. <cfset local.data["TextBody"] = local.data["Body"] />
  79. </cfif>
  80. <!--- Now, we need to look for any other mail parts which may have been speficied. --->
  81. <!--- These will override any body content for the specific type which may have been set before. --->
  82. <!--- Loop over any mailports in the payload. --->
  83. <cfloop array="#arguments.mail.getMailParts()#" index="local.mailpart">
  84. <!--- We need to check the format of the mail part. --->
  85. <cfif local.mailpart.type EQ "html">
  86. <!--- This is an html mail part. --->
  87. <!--- Set the html body for the email as the content for this parameter. --->
  88. <cfset LOCAL.Data["HtmlBody"] = local.mailpart.body />
  89. <cfelseif local.mailpart.type EQ "plain" OR local.mailpart.type EQ "text">
  90. <!--- This is an text mail part. --->
  91. <!--- Set the html body for the email as the content for this parameter. --->
  92. <cfset local.data["TextBody"] = local.mailpart.body />
  93. </cfif>
  94. </cfloop>
  95. <!--- Render the email data as JSON. --->
  96. <!--- This is the format that PostMark like to receieve it in. --->
  97. <cfset local.jsonPacket = serializeJson(local.data) />
  98. <!--- Coldfusion can sometimes add a profix to the JSON, so we're going to clean it up. --->
  99. <cfset local.jsonPacketWithoutPrefix = Trim(Mid(local.jsonPacket, Find("{", local.jsonPacket), len(local.jsonPacket))) />
  100. <!--- We now send the JSON request to the PostMark web service API. --->
  101. <!--- This request has the possibility of failing, so we'll wrap it in a try/catch. --->
  102. <cftry>
  103. <!--- Post our request over to the API. --->
  104. <cfhttp url="https://api.postmarkapp.com/email" method="post" result="local.cfhttp" throwOnError="true">
  105. <cfhttpparam type="header" name="Accept" value="application/json" />
  106. <cfhttpparam type="header" name="Content-type" value="application/json" />
  107. <cfhttpparam type="header" name="X-Postmark-Server-Token" value="#getProperty("ApiKey")#" />
  108. <cfhttpparam type="body" encoded="no" value="#local.jsonPacketWithoutPrefix#" />
  109. </cfhttp>
  110. <!--- Catch any exceptions which might be thrown by this requets. --->
  111. <cfcatch type="any">
  112. <!--- This probably means that something substantial has gone wrong. --->
  113. <!--- We'll append the details of the error into an error structure. --->
  114. <cfset arrayAppend(local.rtnStruct.errorArray,"Error sending mail. #cfcatch.message# : #cfcatch.detail# : #cfcatch.stackTrace#") />
  115. <!--- Return this structure, this will abort any further processing from the plugin. --->
  116. <cfreturn local.rtnStruct />
  117. </cfcatch>
  118. </cftry>
  119. <!--- If we've made it this far then our request to PostMark has been completed. --->
  120. <!--- We can now reformat this result into something which is recognisable to a ColdBox application. --->
  121. <cfset local.rtnStruct = reformatPostMarkResponse(deserializeJSON(local.cfhttp.FileContent.toString())) />
  122. <!--- Return the result from the request. --->
  123. <cfreturn local.rtnStruct />
  124. </cffunction>
  125. <!------------------------------------------- PRIVATE ------------------------------------------>
  126. <cffunction name="encodeHeader" access="private" returntype="struct" hint="I encode named headers so that PostMark likes it">
  127. <cfargument name="MailParam" required="true" type="struct" hint="I'm the file path for the attachment." />
  128. <!--- Create a temporary local structure. --->
  129. <cfset var local = structNew() />
  130. <!--- This is a named custom header. Build a structure for it. --->
  131. <cfset local.this_header = structNew() />
  132. <!--- Add the name and value to this structure. --->
  133. <cfset local.this_header["name"] = arguments.mailparam["name"] />
  134. <cfset local.this_header["value"] = arguments.mailparam["value"] />
  135. <!--- Return the structure which defines the header. --->
  136. <cfreturn local.this_header />
  137. </cffunction>
  138. <cffunction name="encodeAttachment" access="private" returntype="struct" hint="I encode an attachment so that PostMark likes it.">
  139. <cfargument name="MailParam" required="true" type="struct" hint="I'm the file path for the attachment." />
  140. <!--- Create a temporary local structure. --->
  141. <cfset var local = structNew() />
  142. <!--- This is an attachement which needs to be added to the email. --->
  143. <!--- Create a structure for this. --->
  144. <cfset local.attachment = structNew() />
  145. <!--- Read the file so we can get it's content. --->
  146. <cffile action="readbinary" file="#arguments.mailparam.file#" variable="local.objBinaryData" />
  147. <!--- Encode the file path. --->
  148. <cfset local.base64 = toBase64(local.objBinaryData) />
  149. <!--- Build the structure. --->
  150. <cfset local.attachment["Name"] = GetFileFromPath(arguments.mailparam.file) />
  151. <cfset local.attachment["Content"] = local.base64 />
  152. <!--- We now need to check if a filetype was given for this attacgment. --->
  153. <cfif structKeyExists(arguments.mailparam, "filetype")>
  154. <!--- A specific file type was given to us, we'll use this. --->
  155. <cfset local.attachment["ContentType"] = arguments.mailparam.filetype />
  156. <cfelse>
  157. <!--- No file type was given, so we'll try to self assess this using the file extension. --->
  158. <cfset local.attachment["ContentType"] = getFileMimeType(arguments.mailparam.file) />
  159. </cfif>
  160. <!--- Return the structure which defines this attachment. --->
  161. <cfreturn local.attachment />
  162. </cffunction>
  163. <cffunction name="getFileMimeType" access="private" returntype="string" output="false" hint="I calculate the MIME type for a given file.">
  164. <cfargument name="filePath" type="string" required="true" hint="I'm the path to the file to be tested." />
  165. <!--- Return the mime type of this file. --->
  166. <cfreturn getPageContext().getServletContext().getMimeType(arguments.filePath) />
  167. </cffunction>
  168. <cffunction name="reformatPostMarkResponse" access="private" returntype="struct" hint="I format the PostMarkApp return result into one which conforms to the coldbox mailer service.">
  169. <cfargument name="PostMarkReturnStruct" required="true" type="struct" hint="I'm the returned structure from postmark" />
  170. <!--- Create a temporary local structure. --->
  171. <cfset var local = structNew() />
  172. <!--- Check to see if this returned message is OK or not. --->
  173. <cfif PostMarkReturnStruct.Message EQ "OK">
  174. <!--- This message was sent just fine. --->
  175. <!--- Set the error result to false. --->
  176. <cfset local.ReturnStruct["error"] = False />
  177. <!--- Create an array for us to put the errors into. --->
  178. <cfset local.ReturnStruct["errorArray"] = arrayNew(1) />
  179. <!--- We're also going to append the message ID. --->
  180. <!--- This is not a value which exists in the standard coldbox library but we'll add it. --->
  181. <cfset local.ReturnStruct["message_id"] = PostMarkReturnStruct["MessageID"] />
  182. <cfelse>
  183. <!--- If this message was not OK then we have an error on our hands. --->
  184. <!--- Create an array for us to put the errors into. --->
  185. <cfset local.ReturnStruct["errorArray"] = arrayNew(1) />
  186. <!--- Set the error variable to true. --->
  187. <cfset local.ReturnStruct["error"] = True />
  188. <!--- We're also going to append the error code and message into the error array. --->
  189. <cfset arrayAppend(local.ReturnStruct["errorArray"], "#PostMarkReturnStruct['ErrorCode']# - #PostMarkReturnStruct['Message']#") />
  190. </cfif>
  191. <!--- Return the reformated structure. --->
  192. <cfreturn local.ReturnStruct />
  193. </cffunction>
  194. </cfcomponent>