Second Life of a Hungarian SharePoint Geek

August 20, 2016

Why can’t We Send E-Mails from the SharePoint JavaScript Client Object Model, and How to Enable this Feature

Filed under: JavaScript, JSCOM, Mails, SP 2013 — Tags: , , , — Peter Holpar @ 22:43

As you might know, we can send e-mails from the SharePoint managed client object model (see code sample below), but not from the JavaScript version of the client object model.

Note: This feature has its own limitations. For example, you can not send mails to any arbitrary external mail addresses, only to the SharePoint users, and you can not add attachments to the mails, just to name a few.

  1. using (var clientContext = new ClientContext("http://YourSharePointServer/"))
  2. {
  3.     var ep = new EmailProperties();
  4.     ep.To = new List<string> { "user1@company.com", "user2@company.com" };
  5.     ep.From = "user3@company.com";
  6.     ep.Body = "body";
  7.     ep.Subject = "subject";
  8.     Utility.SendEmail(clientContext, ep);
  9.     clientContext.ExecuteQuery();
  10. }

However, if you would like to send mails using the JavaScript client object model, you find quickly, that there is no sendEmail method defined under the methods of the SP.Utilities.Utility class. That means, if you really have to send a mail from your web page using JavaScript, and would like to work only with the out-of-the-box features of SharePoint, you have to use the OData / REST interface, as illustrated by the code sample in this forum thread or see this one if you need additional mail headers.

But wait a minute. As far as I know, both the managed client object model and its JavaScript counterpart use the same server-side components and the same communication protocol between the server and client side. So what is that limitation in this case? Personally, I prefer to useing the JavaScript client object model to invoking the REST methods. Why should I mix them if my other components are written for the JavaScript client object model?

After having several questions on SharePoint StackExchange in the previous days regarding e-mail sending from JavaScript, I’ve decided to look behind the scenes.

As a first step, I searched for the JavaScript implementation of the SP.Utilities.EmailProperties and the SP.Utilities.Utility classes and found them in SP.debug.js (and in SP.js, of course), and really, there is no sendEmail method defined for the SP.Utilities.Utility class.

The mail sending for the client components is implemented on the servers side in the internal static SendEmail_Client method of  the Microsoft.SharePoint.Utilities.SPUtility class (Microsoft.SharePoint assembly). This method has the ClientCallableMethod attribute as follows:

[ClientCallableMethod(Name="SendEmail", OperationType=OperationType.Read, ClientLibraryTargets=ClientLibraryTargets.RESTful | ClientLibraryTargets.Silverlight | ClientLibraryTargets.DotNetFramework)]

You can see, that as the value of the ClientLibraryTargets property it does not define either ClientLibraryTargets.JavaScript or ClientLibraryTargets.All. It means, that it is not intended to be used from JavaScript.

In the rest of the post I show you a few alternatives, how to enable this missing functionality.

Note: The workarounds included in the post are provided “as is”, without any responsibility. Whether they work for you or not may depend on the patch level of your SharePoint environment, and any new installed patch may make a solution build on these workaround unusable. So I suggest you to not use this approach in a productive environment.

I’ve created a new web part page on my site, and added a Script Editor Web Part to it. I set the following content for the new web part:

/_layouts/15/sp.runtime.js
/_layouts/15/sp.js
/SiteAssets/sendMail.js

<button onclick="sendMail()" type="button">Send mail</button>

In the Site Assets library of the site I’ve created a new text file called sendMail.js, and edited its content.

After studying the existing static methods of the SP.Utilities.Utility class in SP.debug.js it was easy to implement the sendEmail method as well. We should have wait, while the loading of  the SP.js file has been finished, then extend the (at this time already) existing SP.Utilities.Utility class with the new method. The next snippet shows our code at this point:

  1. 'use strict';
  2.  
  3. function main() {  
  4.     SP.Utilities.Utility.sendEmail = function SP_Utilities_Utility$resolvePrincipal(context, properties) {
  5.         if (!context) {
  6.             throw Error.argumentNull('context');
  7.         }
  8.         var $v_0 = new SP.ClientActionInvokeStaticMethod(context, '{16f43e7e-bf35-475d-b677-9dc61e549339}', 'SendEmail', [properties]);
  9.  
  10.         context.addQuery($v_0);
  11.     };
  12. }
  13.  
  14. function sendMail() {
  15.     var ctx = SP.ClientContext.get_current();
  16.  
  17.     var emailProperties = new SP.Utilities.EmailProperties();
  18.     emailProperties.set_to(['user1@company.com', 'user2@company.com']);
  19.     emailProperties.set_from('user3@company.com');
  20.     emailProperties.set_body('body');
  21.     emailProperties.set_subject('subject');
  22.  
  23.     SP.Utilities.Utility.sendEmail(ctx, emailProperties);
  24.  
  25.     ctx.executeQueryAsync(
  26.                 function () {
  27.                     console.log("Mail sent");
  28.                 },
  29.                 function (sender, args) {
  30.                     console.log('Request failed. ' + args.get_message() + '\n' + args.get_stackTrace());
  31.                 }
  32.             );
  33.  
  34. }
  35.  
  36.  
  37. SP.SOD.executeOrDelayUntilScriptLoaded(main, "sp.js");

Unfortunately, if you try out the page, and clicks the “Send mail” button, this code sends no mail, but you get an error instead:

Object doesn’t support property or method ‘get_bCC

The corresponding stack trace:

SP.DataConvert.invokeGetProperty [Line: 2, Col: 18240], sp.runtime.js
SP.DataConvert.writePropertiesToXml [Line: 2, Col: 11813], sp.runtime.js
SP.Utilities.EmailProperties.prototype.writeToXml [Line: 2, Col: 441169], sp.js
SP.DataConvert.writeValueToXmlElement [Line: 2, Col: 14393], sp.runtime.js
SP.ClientActionInvokeStaticMethod.prototype.$i_1 [Line: 2, Col: 30238], sp.runtime.js
SP.ClientActionInvokeStaticMethod [Line: 2, Col: 29671], sp.runtime.js
SP_Utilities_Utility$resolvePrincipal [Line: 8, Col: 9], sendMail.js
sendMail [Line: 23, Col: 5], sendMail.js
onclick [Line: 610, Col: 18], SendMailTest.aspx

If you check the property names of the SP.Utilities.EmailProperties class, you see that there are properties like BCC and CC. The corresponding getter and setter method definitions of the same class in the SP.debug.js file according to it have the name get_BCC / set_BCC and get_CC / set_CC.

The problem is, that the SP.DataConvert.invokeGetProperty mehtod calls a private method of the SP.DataConvert class, that – due to the name convention used in the JavaScript client object model – converts the property name BCC to bCC (and CC to cC), converting the first letter to lower case.

SP.DataConvert.$2V=function(a){ULSnd3:;return a.substr(0,1).toLowerCase()+a.substr(1)}    function(a){ULSnd3:;return a.substr(0,1).toLowerCase()+a.substr(1)}

What can we do? There are fortunately several options!

In the fist case, we simply create a new SP.Utilities.EmailProperties instance as earlier, then decorate the new instance in the AddMethods method with the getter / setter methods required by the SP.DataConvert.invokeGetProperty mehtod before sending the mail via the sendEmail method. In these new methods we simply wrap the original methods (the ones with the full uppercase property names).

  1. 'use strict';
  2.  
  3. function main() {  
  4.     SP.Utilities.Utility.sendEmail = function SP_Utilities_Utility$resolvePrincipal(context, properties) {
  5.         if (!context) {
  6.             throw Error.argumentNull('context');
  7.         }
  8.         var $v_0 = new SP.ClientActionInvokeStaticMethod(context, '{16f43e7e-bf35-475d-b677-9dc61e549339}', 'SendEmail', [properties]);
  9.  
  10.         context.addQuery($v_0);
  11.     };
  12. }
  13.  
  14. function AddMethods(emailProps) {
  15.     emailProps.get_bCC = function SP_Utilities_EmailProperties$get_bCC() {
  16.         return emailProps.get_BCC();
  17.     };
  18.     emailProps.set_bCC = function SP_Utilities_EmailProperties$set_bCC(value) {
  19.         emailProps.get_BCC(value);
  20.         return value;
  21.     };
  22.     emailProps.get_cC = function SP_Utilities_EmailProperties$get_cC() {
  23.         return emailProps.get_CC()
  24.     };
  25.     emailProps.set_cC = function SP_Utilities_EmailProperties$set_cC(value) {
  26.         emailProps.get_CC(value)
  27.         return value;
  28.     };
  29. }
  30.  
  31. function sendMail() {
  32.     var ctx = SP.ClientContext.get_current();
  33.  
  34.     var emailProperties = new SP.Utilities.EmailProperties();
  35.     AddMethods(emailProperties);
  36.  
  37.     emailProperties.set_to(['user1@company.com', 'user2@company.com']);
  38.     emailProperties.set_from('user3@company.com');
  39.     emailProperties.set_body('body');
  40.     emailProperties.set_subject('subject');
  41.  
  42.     SP.Utilities.Utility.sendEmail(ctx, emailProperties);
  43.  
  44.     ctx.executeQueryAsync(
  45.                 function () {
  46.                     console.log("Mail sent");
  47.                 },
  48.                 function (sender, args) {
  49.                     console.log('Request failed. ' + args.get_message() + '\n' + args.get_stackTrace());
  50.                 }
  51.             );
  52.  
  53. }
  54.  
  55. SP.SOD.executeOrDelayUntilScriptLoaded(main, "sp.js");

This approach works, but you should invoke the AddMethods method for each of the SP.Utilities.EmailProperties instance you create.

As another option, we can take the source code of SP.Utilities.EmailProperties class from the SP.debug.js file, copy it to our own sendMail.js file, then replace the EmailProperties with EmailPropertiesCustom. Finally, fix the wrong method names.

  1. 'use strict';
  2.  
  3. SP.Utilities.EmailPropertiesCustom = function SP_Utilities_EmailPropertiesCustom() {
  4.     SP.Utilities.EmailPropertiesCustom.initializeBase(this);
  5. };
  6. SP.Utilities.EmailPropertiesCustom.prototype = {
  7.     $1u_1: null,
  8.     $22_1: null,
  9.     $C_1: null,
  10.     $24_1: null,
  11.     $2T_1: null,
  12.     $31_1: null,
  13.     $36_1: null,
  14.     get_additionalHeaders: function SP_Utilities_EmailPropertiesCustom$get_additionalHeaders() {
  15.         return this.$1u_1;
  16.     },
  17.     set_additionalHeaders: function SP_Utilities_EmailPropertiesCustom$set_additionalHeaders(value) {
  18.         this.$1u_1 = value;
  19.         return value;
  20.     },
  21.     get_bCC: function SP_Utilities_EmailPropertiesCustom$get_bCC() {
  22.         return this.$22_1;
  23.     },
  24.     set_bCC: function SP_Utilities_EmailPropertiesCustom$set_bCC(value) {
  25.         this.$22_1 = value;
  26.         return value;
  27.     },
  28.     get_body: function SP_Utilities_EmailPropertiesCustom$get_body() {
  29.         return this.$C_1;
  30.     },
  31.     set_body: function SP_Utilities_EmailPropertiesCustom$set_body(value) {
  32.         this.$C_1 = value;
  33.         return value;
  34.     },
  35.     get_cC: function SP_Utilities_EmailPropertiesCustom$get_cC() {
  36.         return this.$24_1;
  37.     },
  38.     set_cC: function SP_Utilities_EmailPropertiesCustom$set_cC(value) {
  39.         this.$24_1 = value;
  40.         return value;
  41.     },
  42.     get_from: function SP_Utilities_EmailPropertiesCustom$get_from() {
  43.         return this.$2T_1;
  44.     },
  45.     set_from: function SP_Utilities_EmailPropertiesCustom$set_from(value) {
  46.         this.$2T_1 = value;
  47.         return value;
  48.     },
  49.     get_subject: function SP_Utilities_EmailPropertiesCustom$get_subject() {
  50.         return this.$31_1;
  51.     },
  52.     set_subject: function SP_Utilities_EmailPropertiesCustom$set_subject(value) {
  53.         this.$31_1 = value;
  54.         return value;
  55.     },
  56.     get_to: function SP_Utilities_EmailPropertiesCustom$get_to() {
  57.         return this.$36_1;
  58.     },
  59.     set_to: function SP_Utilities_EmailPropertiesCustom$set_to(value) {
  60.         this.$36_1 = value;
  61.         return value;
  62.     },
  63.     get_typeId: function SP_Utilities_EmailPropertiesCustom$get_typeId() {
  64.         return '{fab1608d-fdfb-4c8c-bb0a-9b9cc3618a15}';
  65.     },
  66.     writeToXml: function SP_Utilities_EmailPropertiesCustom$writeToXml(writer, serializationContext) {
  67.         if (!writer) {
  68.             throw Error.argumentNull('writer');
  69.         }
  70.         if (!serializationContext) {
  71.             throw Error.argumentNull('serializationContext');
  72.         }
  73.         var $v_0 = ['AdditionalHeaders', 'BCC', 'Body', 'CC', 'From', 'Subject', 'To'];
  74.  
  75.         SP.DataConvert.writePropertiesToXml(writer, this, $v_0, serializationContext);
  76.         SP.ClientValueObject.prototype.writeToXml.call(this, writer, serializationContext);
  77.     },
  78.     initPropertiesFromJson: function SP_Utilities_EmailPropertiesCustom$initPropertiesFromJson(parentNode) {
  79.         SP.ClientValueObject.prototype.initPropertiesFromJson.call(this, parentNode);
  80.         var $v_0;
  81.  
  82.         $v_0 = parentNode.AdditionalHeaders;
  83.         if (!SP.ScriptUtility.isUndefined($v_0)) {
  84.             this.$1u_1 = SP.DataConvert.fixupType(null, $v_0);
  85.             delete parentNode.AdditionalHeaders;
  86.         }
  87.         $v_0 = parentNode.BCC;
  88.         if (!SP.ScriptUtility.isUndefined($v_0)) {
  89.             this.$22_1 = SP.DataConvert.fixupType(null, $v_0);
  90.             delete parentNode.BCC;
  91.         }
  92.         $v_0 = parentNode.Body;
  93.         if (!SP.ScriptUtility.isUndefined($v_0)) {
  94.             this.$C_1 = $v_0;
  95.             delete parentNode.Body;
  96.         }
  97.         $v_0 = parentNode.CC;
  98.         if (!SP.ScriptUtility.isUndefined($v_0)) {
  99.             this.$24_1 = SP.DataConvert.fixupType(null, $v_0);
  100.             delete parentNode.CC;
  101.         }
  102.         $v_0 = parentNode.From;
  103.         if (!SP.ScriptUtility.isUndefined($v_0)) {
  104.             this.$2T_1 = $v_0;
  105.             delete parentNode.From;
  106.         }
  107.         $v_0 = parentNode.Subject;
  108.         if (!SP.ScriptUtility.isUndefined($v_0)) {
  109.             this.$31_1 = $v_0;
  110.             delete parentNode.Subject;
  111.         }
  112.         $v_0 = parentNode.To;
  113.         if (!SP.ScriptUtility.isUndefined($v_0)) {
  114.             this.$36_1 = SP.DataConvert.fixupType(null, $v_0);
  115.             delete parentNode.To;
  116.         }
  117.     }
  118. };
  119.  
  120. SP.Utilities.EmailPropertiesCustom.registerClass('SP.Utilities.EmailPropertiesCustom', SP.ClientValueObject);
  121.  
  122. function main() {  
  123.     SP.Utilities.Utility.sendEmail = function SP_Utilities_Utility$resolvePrincipal(context, properties) {
  124.         if (!context) {
  125.             throw Error.argumentNull('context');
  126.         }
  127.         var $v_0 = new SP.ClientActionInvokeStaticMethod(context, '{16f43e7e-bf35-475d-b677-9dc61e549339}', 'SendEmail', [properties]);
  128.  
  129.         context.addQuery($v_0);
  130.     };
  131. }
  132.  
  133. function sendMail() {
  134.     var ctx = SP.ClientContext.get_current();
  135.  
  136.     // note: we use our custom class in this case!
  137.     var emailProperties = new SP.Utilities.EmailPropertiesCustom();
  138.  
  139.     emailProperties.set_to(['user1@company.com', 'user2@company.com']);
  140.     emailProperties.set_from('user3@company.com');
  141.     emailProperties.set_body('body');
  142.     emailProperties.set_subject('subject');
  143.  
  144.     SP.Utilities.Utility.sendEmail(ctx, emailProperties);
  145.  
  146.     ctx.executeQueryAsync(
  147.                 function () {
  148.                     console.log("Mail sent");
  149.                 },
  150.                 function (sender, args) {
  151.                     console.log('Request failed. ' + args.get_message() + '\n' + args.get_stackTrace());
  152.                 }
  153.             );
  154.  
  155. }
  156.  
  157. SP.SOD.executeOrDelayUntilScriptLoaded(main, "sp.js");

This approach works either, however you must not forget to create an instance of the SP.Utilities.EmailPropertiesCustom class instead of an SP.Utilities.EmailProperties instance, and pass it as a parameter when invoking the sendEmail method.

In the last approach we take the source code of SP.Utilities.EmailProperties class from the SP.debug.js file, copy it to our own sendMail.js file again. This time, however, we don’t change the class name. As this class is already registered earlier in the SP.js, we would get an error like this:

SCRIPT5022: Sys.InvalidOperationException: Type SP.Utilities.EmailProperties has already been registered. The type may be defined multiple times or the script file that defines it may have already been loaded. A possible cause is a change of settings during a partial update.

To avoid this error, we should first remove the existing registration. This is possible by this line of code:

Sys.__registeredTypes[‘SP.Utilities.EmailProperties’] = false;

The code snippet for the third option:

  1. 'use strict';
  2.  
  3. SP.Utilities.EmailProperties = function SP_Utilities_EmailProperties() {
  4.     SP.Utilities.EmailProperties.initializeBase(this);
  5. };
  6. SP.Utilities.EmailProperties.prototype = {
  7.     $1u_1: null,
  8.     $22_1: null,
  9.     $C_1: null,
  10.     $24_1: null,
  11.     $2T_1: null,
  12.     $31_1: null,
  13.     $36_1: null,
  14.     get_additionalHeaders: function SP_Utilities_EmailProperties$get_additionalHeaders() {
  15.         return this.$1u_1;
  16.     },
  17.     set_additionalHeaders: function SP_Utilities_EmailProperties$set_additionalHeaders(value) {
  18.         this.$1u_1 = value;
  19.         return value;
  20.     },
  21.     get_bCC: function SP_Utilities_EmailProperties$get_bCC() {
  22.         return this.$22_1;
  23.     },
  24.     set_bCC: function SP_Utilities_EmailProperties$set_bCC(value) {
  25.         this.$22_1 = value;
  26.         return value;
  27.     },
  28.     get_body: function SP_Utilities_EmailProperties$get_body() {
  29.         return this.$C_1;
  30.     },
  31.     set_body: function SP_Utilities_EmailProperties$set_body(value) {
  32.         this.$C_1 = value;
  33.         return value;
  34.     },
  35.     get_cC: function SP_Utilities_EmailProperties$get_cC() {
  36.         return this.$24_1;
  37.     },
  38.     set_cC: function SP_Utilities_EmailProperties$set_cC(value) {
  39.         this.$24_1 = value;
  40.         return value;
  41.     },
  42.     get_from: function SP_Utilities_EmailProperties$get_from() {
  43.         return this.$2T_1;
  44.     },
  45.     set_from: function SP_Utilities_EmailProperties$set_from(value) {
  46.         this.$2T_1 = value;
  47.         return value;
  48.     },
  49.     get_subject: function SP_Utilities_EmailProperties$get_subject() {
  50.         return this.$31_1;
  51.     },
  52.     set_subject: function SP_Utilities_EmailProperties$set_subject(value) {
  53.         this.$31_1 = value;
  54.         return value;
  55.     },
  56.     get_to: function SP_Utilities_EmailProperties$get_to() {
  57.         return this.$36_1;
  58.     },
  59.     set_to: function SP_Utilities_EmailProperties$set_to(value) {
  60.         this.$36_1 = value;
  61.         return value;
  62.     },
  63.     get_typeId: function SP_Utilities_EmailProperties$get_typeId() {
  64.         return '{fab1608d-fdfb-4c8c-bb0a-9b9cc3618a15}';
  65.     },
  66.     writeToXml: function SP_Utilities_EmailProperties$writeToXml(writer, serializationContext) {
  67.         if (!writer) {
  68.             throw Error.argumentNull('writer');
  69.         }
  70.         if (!serializationContext) {
  71.             throw Error.argumentNull('serializationContext');
  72.         }
  73.         var $v_0 = ['AdditionalHeaders', 'BCC', 'Body', 'CC', 'From', 'Subject', 'To'];
  74.  
  75.         SP.DataConvert.writePropertiesToXml(writer, this, $v_0, serializationContext);
  76.         SP.ClientValueObject.prototype.writeToXml.call(this, writer, serializationContext);
  77.     },
  78.     initPropertiesFromJson: function SP_Utilities_EmailProperties$initPropertiesFromJson(parentNode) {
  79.         SP.ClientValueObject.prototype.initPropertiesFromJson.call(this, parentNode);
  80.         var $v_0;
  81.  
  82.         $v_0 = parentNode.AdditionalHeaders;
  83.         if (!SP.ScriptUtility.isUndefined($v_0)) {
  84.             this.$1u_1 = SP.DataConvert.fixupType(null, $v_0);
  85.             delete parentNode.AdditionalHeaders;
  86.         }
  87.         $v_0 = parentNode.BCC;
  88.         if (!SP.ScriptUtility.isUndefined($v_0)) {
  89.             this.$22_1 = SP.DataConvert.fixupType(null, $v_0);
  90.             delete parentNode.BCC;
  91.         }
  92.         $v_0 = parentNode.Body;
  93.         if (!SP.ScriptUtility.isUndefined($v_0)) {
  94.             this.$C_1 = $v_0;
  95.             delete parentNode.Body;
  96.         }
  97.         $v_0 = parentNode.CC;
  98.         if (!SP.ScriptUtility.isUndefined($v_0)) {
  99.             this.$24_1 = SP.DataConvert.fixupType(null, $v_0);
  100.             delete parentNode.CC;
  101.         }
  102.         $v_0 = parentNode.From;
  103.         if (!SP.ScriptUtility.isUndefined($v_0)) {
  104.             this.$2T_1 = $v_0;
  105.             delete parentNode.From;
  106.         }
  107.         $v_0 = parentNode.Subject;
  108.         if (!SP.ScriptUtility.isUndefined($v_0)) {
  109.             this.$31_1 = $v_0;
  110.             delete parentNode.Subject;
  111.         }
  112.         $v_0 = parentNode.To;
  113.         if (!SP.ScriptUtility.isUndefined($v_0)) {
  114.             this.$36_1 = SP.DataConvert.fixupType(null, $v_0);
  115.             delete parentNode.To;
  116.         }
  117.     }
  118. };
  119.  
  120. // re-register the type
  121. // to avoid the error
  122. // SCRIPT5022: Sys.InvalidOperationException: Type SP.Utilities.EmailProperties has already been registered. The type may be defined multiple times or the script file that defines it may have already been loaded. A possible cause is a change of settings during a partial update.
  123. // we should first remove the existing registration
  124. Sys.__registeredTypes['SP.Utilities.EmailProperties'] = false;
  125. SP.Utilities.EmailProperties.registerClass('SP.Utilities.EmailProperties', SP.ClientValueObject);
  126.  
  127. function main() {  
  128.     SP.Utilities.Utility.sendEmail = function SP_Utilities_Utility$resolvePrincipal(context, properties) {
  129.         if (!context) {
  130.             throw Error.argumentNull('context');
  131.         }
  132.         var $v_0 = new SP.ClientActionInvokeStaticMethod(context, '{16f43e7e-bf35-475d-b677-9dc61e549339}', 'SendEmail', [properties]);
  133.  
  134.         context.addQuery($v_0);
  135.     };
  136. }
  137.  
  138. function sendMail() {
  139.     var ctx = SP.ClientContext.get_current();
  140.     
  141.     var emailProperties = new SP.Utilities.EmailProperties();
  142.     emailProperties.set_to(['user1@company.com', 'user2@company.com']);
  143.     emailProperties.set_from('user3@company.com');
  144.     emailProperties.set_body('body');
  145.     emailProperties.set_subject('subject');
  146.  
  147.     SP.Utilities.Utility.sendEmail(ctx, emailProperties);
  148.  
  149.     ctx.executeQueryAsync(
  150.                 function () {
  151.                     console.log("Mail sent");
  152.                 },
  153.                 function (sender, args) {
  154.                     console.log('Request failed. ' + args.get_message() + '\n' + args.get_stackTrace());
  155.                 }
  156.             );
  157.  
  158. }
  159.  
  160. SP.SOD.executeOrDelayUntilScriptLoaded(main, "sp.js");

Although it is possible the less supported option (if there are different levels of supportability at all) from the three options discussed in the post, I prefer this last one, as its usage is the most transparent for the developer. One can use the SP.Utilities.EmailProperties class and there is no need for invoking helper methods.

Note: Be aware, that when using one of the last two options, you should call the get_bCC / set_bCC and get_cC / set_cC methods instead of the get_BCC / set_BCC and get_CC / set_CC methods if you need to read / set the BCC / CC properties. In the case of the first option however, you can call both of the methods with first uppercase letter, and the methods with first lowercase letter.

Advertisements

July 2, 2015

Sending mails from a test SharePoint system, Updated for SharePoint 2013

Filed under: Mails, Reflection, SP 2013 — Tags: , , — Peter Holpar @ 23:48

Last year I published a post about how to redirect mails sent from your SharePoint application to a list, storing the mails as attachments for the list items. Unfortunately, the Reflection-based approach described there seems to no longer work in SharePoint 2013. My goal with the current post is to provide an alternative solution to the problem.

The first issue is with the signature of the private Send method of the MailMessage class we used to save the content of the mail into a MemoryStream: it has been extended with an extra bool parameter (allowUnicode) since the .NET  4.5 version. This problem could be easily fixed by altering the method invocation with the extra parameter, as:

sendMethod.Invoke(message, BindingFlags.Instance | BindingFlags.NonPublic, null, new[] { mailWriter, true, true }, null);

If you test the code in a simple console application, it works, the mail message will be saved into the MemoryStream. However, if you try to use the same code from a SharePoint application (an application referencing the server side SharePoint assemblies: either a console application or web part), you become a nasty exception:

An unhandled exception of type ‘System.AccessViolationException’ occurred
Additional information: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.

After a short research, it turned out that the reason is the x64 process the SharePoint server side assemblies require. Since we can not change that requirement, I had to find an alternative solution. It was easy, as the stackoverflow thread the original solution originate from already contains an other option that plays with the DeliveryMethod of the SmtpClient class, saving the mail into a file in a temporary folder in the file system. Based on that answer I’ve altered the code of my DummyMailSender.

First, I’ve defined the helper class TemporaryDirectory to clean up the rest after the work:

  1. internal class TemporaryDirectory : IDisposable
  2. {
  3.     public TemporaryDirectory()
  4.     {
  5.         DirectoryPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
  6.         Directory.CreateDirectory(DirectoryPath);
  7.     }
  8.  
  9.     public string DirectoryPath { get; private set; }
  10.  
  11.     public void Dispose()
  12.     {
  13.         if (Directory.Exists(DirectoryPath))
  14.         {
  15.             Directory.Delete(DirectoryPath, true);
  16.         }
  17.     }
  18. }

and another class Extensions that implements an extension method to save the mail into a byte array:

  1. internal static class Extensions
  2. {
  3.     public static byte[] GetAsByteArray(this MailMessage m)
  4.     {
  5.         byte[] messageData = null;
  6.  
  7.         var smtpClient = new SmtpClient { DeliveryMethod = SmtpDeliveryMethod.SpecifiedPickupDirectory };
  8.  
  9.         using (var tempDir = new TemporaryDirectory())
  10.         {
  11.             smtpClient.PickupDirectoryLocation = tempDir.DirectoryPath;
  12.             smtpClient.Send(m);
  13.             var emlFile = Directory.GetFiles(smtpClient.PickupDirectoryLocation).FirstOrDefault();
  14.             if (emlFile != null)
  15.             {
  16.                 // read all file contents and trim the carriage return / new line at the end
  17.                 var messageAsText = File.ReadAllText(emlFile).Trim();
  18.                 System.Text.ASCIIEncoding enc = new System.Text.ASCIIEncoding();
  19.                 messageData = enc.GetBytes(messageAsText);
  20.             }
  21.         }
  22.         return messageData;
  23.     }
  24. }

Having these classes that perform the bulk of the functionality, the DummyMailSender can be simplified as:

  1. public void Send(MailMessage message)
  2. {
  3.     Trace.TraceInformation("Mail (subject: '{0}') sending started via DummyMailSender", message.Subject);
  4.  
  5.     byte[] messageData = message.GetAsByteArray();
  6.  
  7.     // RootWeb mustn't be disposed, see:
  8.     // http://blogs.msdn.com/b/rogerla/archive/2008/10/04/updated-spsite-rootweb-dispose-guidance.aspx
  9.     SPWeb rootWeb = _site.RootWeb;
  10.     bool allowUnsafeOriginal = rootWeb.AllowUnsafeUpdates;
  11.     try
  12.     {
  13.         rootWeb.AllowUnsafeUpdates = true;
  14.         SPList mailList = rootWeb.Lists[Constants.DummyMailList];
  15.         SPListItem mailItem = mailList.AddItem();
  16.         mailItem[SPBuiltInFieldId.Title] = message.Subject;
  17.         mailItem.Attachments.Add("Mail.eml", messageData);
  18.         mailItem.Update();
  19.  
  20.         string mailUrl = string.Format("{0}{1}?ID={2}", _site.Url, mailList.DefaultDisplayFormUrl, mailItem.ID);
  21.         Trace.TraceInformation("Dummy mail with subject '{0}' \"sent\" to '{1}' (cc: '{2}'), saved to '{3}'", message.Subject, message.To, message.CC, mailUrl);
  22.     }
  23.     catch (Exception ex)
  24.     {
  25.         Trace.TraceError("Error sending dummy mail: {0}\r\n{1}", ex.Message, ex.StackTrace);
  26.     }
  27.     finally
  28.     {
  29.         rootWeb.AllowUnsafeUpdates = allowUnsafeOriginal;
  30.     }
  31. }

This new version of DummyMailSender might perform not so well, as the original, Reflection-based one, but it should not be a major issue in a test system it was planned for, and at least it is a supported solution. At least, you don’t have to worry about that it won’t work after a newer .NET Framework version.

March 22, 2014

The ‘Date modified’ of my .eml Files got Changed When I First Select The File. Why?

Filed under: Alternate Data Streams, Mails, NTFS, Windows — Tags: , , , — Peter Holpar @ 20:44

In my last post I wrote about a rather odd behavior one can experience when working with e-mail files (.eml), namely, that if you select the file the first time in Windows Explorer (either by a mouse click or by navigation via the keyboard) its ‘Date modified’ gets updated with the current date. In this short post I plan to show you the reason behind and a few nice-to-knows around that.

As far as I understood, it’s all about the Alternate Data Streams (ADS) feature of the NTFS file system. As you might know besides the main data stream of a file, NTFS supports these alternate streams, for example, to store extra properties of the file, that are intended to be edited automatically by the system or via alternate editors, but not editable by altering the binary data of the file itself.

You can access these streams easily via PowerShell 3.0, but if you have no PS 3 yet, no problem, you can use standard Windows Command prompt tools as well. There are other tools to manipulate the stream as well, like streams from Sysinternals.

The .eml files may have an alternate stream of type Outlook Express Properties Stream which looks like sample.eml:OECustomProperty:$DATA. However, this stream is not automatically created as you save the .eml file, just after selecting one that does not have yet any, like selecting it the first time. To tell the truth, I don’t see the design decision behind this behavior, it smells rather a bug to me. As the new data stream gets created, the last modified date of the file is updated as well.

For example, if you list the .eml files with their data streams using the “dir /r” you might get something like this:

image

As you see, most of the files (the ones I’ve already selected in Windows Explorer) already have the OECustomProperty:$DATA stream, although a couple of them (like c5a6593e-806a-43dc-8509-bd4072a26f6b.eml and c6b834a1-b1d8-4148-a9df-0c32b75fd206.eml) haven’t yet any. If you select those latter files in Windows Explorer, their alternate stream gets created and the last modified date will be updated.

But what is within OECustomProperty:$DATA stream?

You can display it, for example from Command Prompt:

more < youremlfile.eml:OECustomProperty:$DATA

You will see several lines of binary information, but should be able to recognize the subject of your mail. If you check the properties of your file, you will see the same information on the Details tab, in the Name property.

image

If you open the .eml file, change the subject of the mail, and save the file, then check the properties again, you will see, that the Name property has not been changed. It is just another evidence that this information is stored in the alternate stream. If we delete the stream (for example using stream.exe –d or via PowerShell 3.0 cmdlet Remove-Item –Stream), and select the file again in Windows Explorer, the stream will be regenerated, and thus the ‘Date modified’ will be set to the current time and the Name property will be refreshed according to the new subject of the mail.

Although you could disable this behavior by inactivating the property handler for the .eml files (see here how to achieve that by deleting the corresponding registry key), I don’t think it is a good idea as it might have other side effects as well.

March 17, 2014

Sending mails from a test SharePoint system, 2nd Edition

Filed under: Mails, Reflection, SP 2010 — Tags: , , — Peter Holpar @ 22:56

Last year I published a post describing how to fake mail sending of your applications in test system, without physically delivering mails via SMTP to addressees. In that post I utilized a solution via the smtp deliveryMethod to store the outgoing mails into a specific pickupDirectoryLocation instead of sending them to an SMTP server.

That solution is useful in simple situation, but have 2 major and 2 minor issues you face to, especially in more advanced topologies:

  1. The users of your system might have no access to the file system of the server. Even if you shared the folder and gave read permission to the users, there may be firewalls or other network components that do not allow accessing the share, just because the ports necessary for file and printer sharing are not open (see SMB, ports 445 and 139). It’s typical in the case of an intranet (like a public facing WCM site) or extranet solution, but may happen is your test and production systems are separated into various physical networks. Even if you could technically open the ports, the security policy of the organization should not allow this configuration change.
  2. If you have multiple applications in your SharePoint web application, and a few of these ones should be able to send out mails while others should not, or you have various processes running in the context of the SharePoint Timer Service (owstimer.exe), like workflows or event receivers conflicting in this requirement, you can not solve the conflict via a simple setting in the configuration.
  3. If you click on the .eml file the first time, the value of the Date modified field is changed to the current time (I explain the reason of this behavior in another post later), so it is more convenient to use the Date created field, and order the files based on the value of this field (newest files on the top).
  4. Even if your users have access to the mails in the file system, it’s not very helpful to see a list of .eml files with GUIDs as file name to choose which file to open (see sample screenshot below).

MailDirectory

You can check the Subject of the mail by opening the .eml file using Outlook or even a simple text editor, or by checking the file properties (Details tab). Neither of these options is very comfortable.

MailProperties

Instead of the configuration-based solution we can use a code-based approach that fulfills even more sophisticated requirements. In this sample I assume the helper classes are part of the application assembly. If you would like to place them into a separate helper assembly, you should consider to alter the internal access modifiers to public.

First we define an interface IMailSender with a single method, Send that has a single parameter of type MailMessage.

  1. interface IMailSender
  2. {
  3.     void Send(MailMessage message);
  4. }

Then we implement this interface in two classes, SmtpMailSender and DummyMailSender. Both of this types have a single public constructor with a single SPSite parameter.

In the SmtpMailSender class we send the mail as usual, through the the SMTP server configured in the OutboundMailServiceInstance of the corresponding web application.

  1. internal class SmtpMailSender : IMailSender
  2. {
  3.     private SPSite _site;
  4.  
  5.     public SmtpMailSender(SPSite site)
  6.     {
  7.         _site = site;
  8.     }
  9.  
  10.     public void Send(MailMessage message)
  11.     {
  12.         Trace.TraceInformation("Mail sending started via SmtpMailSender");
  13.  
  14.         string smtpHost = _site.WebApplication.OutboundMailServiceInstance.Server.Address;
  15.         SmtpClient smtpClient = new SmtpClient
  16.         {
  17.             Host = smtpHost
  18.         };
  19.         smtpClient.Send(message);
  20.  
  21.         Trace.TraceInformation("Mail sent to '{0}' via '{1}'", message.To, smtpHost);
  22.     }
  23. }

In the DummyMailSender class we read the content of the mail as a byte array, and store it in a SharePoint list (name of the list is stored in the GlobalConst.DummyMailList) as an attachment of an item that has its title as the mail subject.

  1. internal class DummyMailSender : IMailSender
  2. {
  3.     private SPSite _site;
  4.  
  5.     public DummyMailSender(SPSite site)
  6.     {
  7.         _site = site;
  8.     }
  9.  
  10.     public void Send(MailMessage message)
  11.     {
  12.         Trace.TraceInformation("Mail sending started via DummyMailSender");
  13.  
  14.         Assembly assembly = typeof(SmtpClient).Assembly;
  15.         Type mailWriterType = assembly.GetType("System.Net.Mail.MailWriter");
  16.  
  17.         // see http://stackoverflow.com/questions/9595440/getting-system-net-mail-mailmessage-as-a-memorystream-in-net-4-5-beta
  18.         byte[] messageData = null;
  19.         using (MemoryStream mailStream = new MemoryStream())
  20.         {
  21.             ConstructorInfo mailWriterContructor = mailWriterType.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, null, new[] { typeof(Stream) }, null);
  22.             object mailWriter = mailWriterContructor.Invoke(new object[] { mailStream });
  23.             MethodInfo sendMethod = typeof(MailMessage).GetMethod("Send", BindingFlags.Instance | BindingFlags.NonPublic);
  24.             sendMethod.Invoke(message, BindingFlags.Instance | BindingFlags.NonPublic, null, new[] { mailWriter, true }, null);
  25.  
  26.             messageData = mailStream.ToArray();
  27.         }
  28.  
  29.         SPList mailList = _site.RootWeb.Lists[GlobalConst.DummyMailList];
  30.         SPListItem mailItem = mailList.AddItem();
  31.         mailItem[SPBuiltInFieldId.Title] = message.Subject;
  32.         mailItem.Attachments.Add("Mail.eml", messageData);
  33.         mailItem.Update();
  34.  
  35.         string mailUrl = string.Format("{0}{1}?ID={2}", _site.Url, mailList.DefaultDisplayFormUrl, mailItem.ID);
  36.         Trace.TraceInformation("Dummy mail \"sent\" to '{0}', saved to '{1}'", message.To, mailUrl);
  37.     }
  38. }

Furthermore, we have a factory class that returns the right implementation of the IMailSender based on a condition evaluated in an extension method called IsTestSystem.

  1. internal static class MailSenderFactory
  2. {
  3.     public static IMailSender GetMailSender(SPSite site)
  4.     {
  5.         IMailSender mailSender = site.IsTestSystem() ? (IMailSender)new DummyMailSender(site) : (IMailSender)new SmtpMailSender(site);
  6.  
  7.         return mailSender;
  8.     }
  9. }

In our case we check if the site URL contains the substring “test”, of course you can use more advanced conditions and configuration options as well, like values stored in the site property bag, etc.

  1. public static bool IsTestSite(this SPSite site)
  2. {
  3.     string siteUrl = (site != null) ? site.Url.ToLower() : string.Empty;
  4.     bool result = siteUrl.Contains("test");
  5.  
  6.     return result;
  7. }

In the code of your application you can utilize these classes like illustrated below:

  1. // prepare mail
  2. MailMessage mail = new MailMessage("sender@company.com", "addressee@company.com");
  3. mail.Subject = "Test message";
  4. mail.Body = "This is the body of a test message";
  5. //Send mail
  6. IMailSender mailSender = MailSenderFactory.GetMailSender(site);
  7. mailSender.Send(mail);

In the test system you have to create manually the list for the mails in the root web. I find it useful, to add the Creation date field to the default view, and order the items based on that field (latest on the top. If you have multiple applications to test, you can use various lists for each one, so it is possible to give permissions only to the users who test the specific application.

You can enable opening .eml files in the browser itself via a PowerShell script like below:

$mimetype = "message/rfc822"
$webapp = Get-SPWebApplication http://yourwebappurl
$webapp.AllowedInlineDownloadedMimeTypes.Add($mimetype)
$webapp.Update()

However, only the body of the message will be displayed in the browser. So if the users need to see other information (like sender, addressee, subject, etc.), they should download the attachment and open it using Outlook.

July 6, 2013

Sending mails from a test SharePoint system

Filed under: Mails, SP 2010 — Tags: , — Peter Holpar @ 17:26

In the past weeks I worked on a few existing applications at a customer. Part of these applications was sending e-mails (for example, from event receivers, workflow, timer jobs or simply from web-pats or application pages). The test / developer systems used the copy of the content database from the production system, only a very limited set of test users were available for testing purposes.

The applications either simply ignored the possibility that they might be running in the test systems and sent mail notifications using SPUtility.SendEmail / System.Net.Mail.SmtpClient, or they contained a hardcoded logic wired up on the naming pattern of the server URLs, like (myserver.company.com / myservertest.company.com / myserverdev.company.com) and sent the mails to a dedicated mail address fro dev. and test systems.

It means, users of the real system might become notifications from the test system (not knowing the source of the mail) regarding a task, that they already processed in the productive system, or we have the mails in the dedicated mailbox without the information who would be the addressee of the mail in the real system. I found both cases bad enough.

Instead of this kind of implementation, one could apply Dependency Injection (for example using Unity) and use different approaches in the different systems. In the productive system, your mail processor may send mails as usual, however in the test and developer system you should save the mails into a file or send them to the dedicated mailbox as an attachment. You should be able to set the active mail processor from configuration, without altering the source code.

However, it might be even simpler, if you can (and usually you must be able to!) rewrite your code to use System.Net.Mail.SmtpClient instead of SPUtility.SendEmail. In the case of SmtpClient there is a less-known feature, namely that you can set the delivery mode not only from code, but also from configuration, like this:

<system.net>
  <mailSettings>
    <smtp deliveryMethod="SpecifiedPickupDirectory">
     <specifiedPickupDirectory pickupDirectoryLocation="C:\somedirectory" />
    </smtp>
  </mailSettings>
</system.net>

If you include this node into the <configuration> section of the web.config and owstimer.exe.config files in the test / developer systems, the mails would not be sent through SMTP to the outgoing mail server, but saved as .eml files into the specified folder instead. In that folder you can check the mail content and addresses, attachments, etc.

Note: Be aware, that the method described here does not apply to the mails sent through SPUtility.SendEmail or standard SharePoint mails, like alerts, notifications when a user becomes new permissions or access requests mails, as SharePoint has its own way of implementation for mail sending that is not built on top of SmtpClient.

Blog at WordPress.com.