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.
- using (var clientContext = new ClientContext("http://YourSharePointServer/"))
- {
- var ep = new EmailProperties();
- ep.To = new List<string> { "user1@company.com", "user2@company.com" };
- ep.From = "user3@company.com";
- ep.Body = "body";
- ep.Subject = "subject";
- Utility.SendEmail(clientContext, ep);
- clientContext.ExecuteQuery();
- }
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:
- 'use strict';
- function main() {
- SP.Utilities.Utility.sendEmail = function SP_Utilities_Utility$resolvePrincipal(context, properties) {
- if (!context) {
- throw Error.argumentNull('context');
- }
- var $v_0 = new SP.ClientActionInvokeStaticMethod(context, '{16f43e7e-bf35-475d-b677-9dc61e549339}', 'SendEmail', [properties]);
- context.addQuery($v_0);
- };
- }
- function sendMail() {
- var ctx = SP.ClientContext.get_current();
- var emailProperties = new SP.Utilities.EmailProperties();
- emailProperties.set_to(['user1@company.com', 'user2@company.com']);
- emailProperties.set_from('user3@company.com');
- emailProperties.set_body('body');
- emailProperties.set_subject('subject');
- SP.Utilities.Utility.sendEmail(ctx, emailProperties);
- ctx.executeQueryAsync(
- function () {
- console.log("Mail sent");
- },
- function (sender, args) {
- console.log('Request failed. ' + args.get_message() + '\n' + args.get_stackTrace());
- }
- );
- }
- 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).
- 'use strict';
- function main() {
- SP.Utilities.Utility.sendEmail = function SP_Utilities_Utility$resolvePrincipal(context, properties) {
- if (!context) {
- throw Error.argumentNull('context');
- }
- var $v_0 = new SP.ClientActionInvokeStaticMethod(context, '{16f43e7e-bf35-475d-b677-9dc61e549339}', 'SendEmail', [properties]);
- context.addQuery($v_0);
- };
- }
- function AddMethods(emailProps) {
- emailProps.get_bCC = function SP_Utilities_EmailProperties$get_bCC() {
- return emailProps.get_BCC();
- };
- emailProps.set_bCC = function SP_Utilities_EmailProperties$set_bCC(value) {
- emailProps.get_BCC(value);
- return value;
- };
- emailProps.get_cC = function SP_Utilities_EmailProperties$get_cC() {
- return emailProps.get_CC()
- };
- emailProps.set_cC = function SP_Utilities_EmailProperties$set_cC(value) {
- emailProps.get_CC(value)
- return value;
- };
- }
- function sendMail() {
- var ctx = SP.ClientContext.get_current();
- var emailProperties = new SP.Utilities.EmailProperties();
- AddMethods(emailProperties);
- emailProperties.set_to(['user1@company.com', 'user2@company.com']);
- emailProperties.set_from('user3@company.com');
- emailProperties.set_body('body');
- emailProperties.set_subject('subject');
- SP.Utilities.Utility.sendEmail(ctx, emailProperties);
- ctx.executeQueryAsync(
- function () {
- console.log("Mail sent");
- },
- function (sender, args) {
- console.log('Request failed. ' + args.get_message() + '\n' + args.get_stackTrace());
- }
- );
- }
- 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.
- 'use strict';
- SP.Utilities.EmailPropertiesCustom = function SP_Utilities_EmailPropertiesCustom() {
- SP.Utilities.EmailPropertiesCustom.initializeBase(this);
- };
- SP.Utilities.EmailPropertiesCustom.prototype = {
- $1u_1: null,
- $22_1: null,
- $C_1: null,
- $24_1: null,
- $2T_1: null,
- $31_1: null,
- $36_1: null,
- get_additionalHeaders: function SP_Utilities_EmailPropertiesCustom$get_additionalHeaders() {
- return this.$1u_1;
- },
- set_additionalHeaders: function SP_Utilities_EmailPropertiesCustom$set_additionalHeaders(value) {
- this.$1u_1 = value;
- return value;
- },
- get_bCC: function SP_Utilities_EmailPropertiesCustom$get_bCC() {
- return this.$22_1;
- },
- set_bCC: function SP_Utilities_EmailPropertiesCustom$set_bCC(value) {
- this.$22_1 = value;
- return value;
- },
- get_body: function SP_Utilities_EmailPropertiesCustom$get_body() {
- return this.$C_1;
- },
- set_body: function SP_Utilities_EmailPropertiesCustom$set_body(value) {
- this.$C_1 = value;
- return value;
- },
- get_cC: function SP_Utilities_EmailPropertiesCustom$get_cC() {
- return this.$24_1;
- },
- set_cC: function SP_Utilities_EmailPropertiesCustom$set_cC(value) {
- this.$24_1 = value;
- return value;
- },
- get_from: function SP_Utilities_EmailPropertiesCustom$get_from() {
- return this.$2T_1;
- },
- set_from: function SP_Utilities_EmailPropertiesCustom$set_from(value) {
- this.$2T_1 = value;
- return value;
- },
- get_subject: function SP_Utilities_EmailPropertiesCustom$get_subject() {
- return this.$31_1;
- },
- set_subject: function SP_Utilities_EmailPropertiesCustom$set_subject(value) {
- this.$31_1 = value;
- return value;
- },
- get_to: function SP_Utilities_EmailPropertiesCustom$get_to() {
- return this.$36_1;
- },
- set_to: function SP_Utilities_EmailPropertiesCustom$set_to(value) {
- this.$36_1 = value;
- return value;
- },
- get_typeId: function SP_Utilities_EmailPropertiesCustom$get_typeId() {
- return '{fab1608d-fdfb-4c8c-bb0a-9b9cc3618a15}';
- },
- writeToXml: function SP_Utilities_EmailPropertiesCustom$writeToXml(writer, serializationContext) {
- if (!writer) {
- throw Error.argumentNull('writer');
- }
- if (!serializationContext) {
- throw Error.argumentNull('serializationContext');
- }
- var $v_0 = ['AdditionalHeaders', 'BCC', 'Body', 'CC', 'From', 'Subject', 'To'];
- SP.DataConvert.writePropertiesToXml(writer, this, $v_0, serializationContext);
- SP.ClientValueObject.prototype.writeToXml.call(this, writer, serializationContext);
- },
- initPropertiesFromJson: function SP_Utilities_EmailPropertiesCustom$initPropertiesFromJson(parentNode) {
- SP.ClientValueObject.prototype.initPropertiesFromJson.call(this, parentNode);
- var $v_0;
- $v_0 = parentNode.AdditionalHeaders;
- if (!SP.ScriptUtility.isUndefined($v_0)) {
- this.$1u_1 = SP.DataConvert.fixupType(null, $v_0);
- delete parentNode.AdditionalHeaders;
- }
- $v_0 = parentNode.BCC;
- if (!SP.ScriptUtility.isUndefined($v_0)) {
- this.$22_1 = SP.DataConvert.fixupType(null, $v_0);
- delete parentNode.BCC;
- }
- $v_0 = parentNode.Body;
- if (!SP.ScriptUtility.isUndefined($v_0)) {
- this.$C_1 = $v_0;
- delete parentNode.Body;
- }
- $v_0 = parentNode.CC;
- if (!SP.ScriptUtility.isUndefined($v_0)) {
- this.$24_1 = SP.DataConvert.fixupType(null, $v_0);
- delete parentNode.CC;
- }
- $v_0 = parentNode.From;
- if (!SP.ScriptUtility.isUndefined($v_0)) {
- this.$2T_1 = $v_0;
- delete parentNode.From;
- }
- $v_0 = parentNode.Subject;
- if (!SP.ScriptUtility.isUndefined($v_0)) {
- this.$31_1 = $v_0;
- delete parentNode.Subject;
- }
- $v_0 = parentNode.To;
- if (!SP.ScriptUtility.isUndefined($v_0)) {
- this.$36_1 = SP.DataConvert.fixupType(null, $v_0);
- delete parentNode.To;
- }
- }
- };
- SP.Utilities.EmailPropertiesCustom.registerClass('SP.Utilities.EmailPropertiesCustom', SP.ClientValueObject);
- function main() {
- SP.Utilities.Utility.sendEmail = function SP_Utilities_Utility$resolvePrincipal(context, properties) {
- if (!context) {
- throw Error.argumentNull('context');
- }
- var $v_0 = new SP.ClientActionInvokeStaticMethod(context, '{16f43e7e-bf35-475d-b677-9dc61e549339}', 'SendEmail', [properties]);
- context.addQuery($v_0);
- };
- }
- function sendMail() {
- var ctx = SP.ClientContext.get_current();
- // note: we use our custom class in this case!
- var emailProperties = new SP.Utilities.EmailPropertiesCustom();
- emailProperties.set_to(['user1@company.com', 'user2@company.com']);
- emailProperties.set_from('user3@company.com');
- emailProperties.set_body('body');
- emailProperties.set_subject('subject');
- SP.Utilities.Utility.sendEmail(ctx, emailProperties);
- ctx.executeQueryAsync(
- function () {
- console.log("Mail sent");
- },
- function (sender, args) {
- console.log('Request failed. ' + args.get_message() + '\n' + args.get_stackTrace());
- }
- );
- }
- 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:
- 'use strict';
- SP.Utilities.EmailProperties = function SP_Utilities_EmailProperties() {
- SP.Utilities.EmailProperties.initializeBase(this);
- };
- SP.Utilities.EmailProperties.prototype = {
- $1u_1: null,
- $22_1: null,
- $C_1: null,
- $24_1: null,
- $2T_1: null,
- $31_1: null,
- $36_1: null,
- get_additionalHeaders: function SP_Utilities_EmailProperties$get_additionalHeaders() {
- return this.$1u_1;
- },
- set_additionalHeaders: function SP_Utilities_EmailProperties$set_additionalHeaders(value) {
- this.$1u_1 = value;
- return value;
- },
- get_bCC: function SP_Utilities_EmailProperties$get_bCC() {
- return this.$22_1;
- },
- set_bCC: function SP_Utilities_EmailProperties$set_bCC(value) {
- this.$22_1 = value;
- return value;
- },
- get_body: function SP_Utilities_EmailProperties$get_body() {
- return this.$C_1;
- },
- set_body: function SP_Utilities_EmailProperties$set_body(value) {
- this.$C_1 = value;
- return value;
- },
- get_cC: function SP_Utilities_EmailProperties$get_cC() {
- return this.$24_1;
- },
- set_cC: function SP_Utilities_EmailProperties$set_cC(value) {
- this.$24_1 = value;
- return value;
- },
- get_from: function SP_Utilities_EmailProperties$get_from() {
- return this.$2T_1;
- },
- set_from: function SP_Utilities_EmailProperties$set_from(value) {
- this.$2T_1 = value;
- return value;
- },
- get_subject: function SP_Utilities_EmailProperties$get_subject() {
- return this.$31_1;
- },
- set_subject: function SP_Utilities_EmailProperties$set_subject(value) {
- this.$31_1 = value;
- return value;
- },
- get_to: function SP_Utilities_EmailProperties$get_to() {
- return this.$36_1;
- },
- set_to: function SP_Utilities_EmailProperties$set_to(value) {
- this.$36_1 = value;
- return value;
- },
- get_typeId: function SP_Utilities_EmailProperties$get_typeId() {
- return '{fab1608d-fdfb-4c8c-bb0a-9b9cc3618a15}';
- },
- writeToXml: function SP_Utilities_EmailProperties$writeToXml(writer, serializationContext) {
- if (!writer) {
- throw Error.argumentNull('writer');
- }
- if (!serializationContext) {
- throw Error.argumentNull('serializationContext');
- }
- var $v_0 = ['AdditionalHeaders', 'BCC', 'Body', 'CC', 'From', 'Subject', 'To'];
- SP.DataConvert.writePropertiesToXml(writer, this, $v_0, serializationContext);
- SP.ClientValueObject.prototype.writeToXml.call(this, writer, serializationContext);
- },
- initPropertiesFromJson: function SP_Utilities_EmailProperties$initPropertiesFromJson(parentNode) {
- SP.ClientValueObject.prototype.initPropertiesFromJson.call(this, parentNode);
- var $v_0;
- $v_0 = parentNode.AdditionalHeaders;
- if (!SP.ScriptUtility.isUndefined($v_0)) {
- this.$1u_1 = SP.DataConvert.fixupType(null, $v_0);
- delete parentNode.AdditionalHeaders;
- }
- $v_0 = parentNode.BCC;
- if (!SP.ScriptUtility.isUndefined($v_0)) {
- this.$22_1 = SP.DataConvert.fixupType(null, $v_0);
- delete parentNode.BCC;
- }
- $v_0 = parentNode.Body;
- if (!SP.ScriptUtility.isUndefined($v_0)) {
- this.$C_1 = $v_0;
- delete parentNode.Body;
- }
- $v_0 = parentNode.CC;
- if (!SP.ScriptUtility.isUndefined($v_0)) {
- this.$24_1 = SP.DataConvert.fixupType(null, $v_0);
- delete parentNode.CC;
- }
- $v_0 = parentNode.From;
- if (!SP.ScriptUtility.isUndefined($v_0)) {
- this.$2T_1 = $v_0;
- delete parentNode.From;
- }
- $v_0 = parentNode.Subject;
- if (!SP.ScriptUtility.isUndefined($v_0)) {
- this.$31_1 = $v_0;
- delete parentNode.Subject;
- }
- $v_0 = parentNode.To;
- if (!SP.ScriptUtility.isUndefined($v_0)) {
- this.$36_1 = SP.DataConvert.fixupType(null, $v_0);
- delete parentNode.To;
- }
- }
- };
- // re-register the type
- // to avoid the error
- // 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.
- // we should first remove the existing registration
- Sys.__registeredTypes['SP.Utilities.EmailProperties'] = false;
- SP.Utilities.EmailProperties.registerClass('SP.Utilities.EmailProperties', SP.ClientValueObject);
- function main() {
- SP.Utilities.Utility.sendEmail = function SP_Utilities_Utility$resolvePrincipal(context, properties) {
- if (!context) {
- throw Error.argumentNull('context');
- }
- var $v_0 = new SP.ClientActionInvokeStaticMethod(context, '{16f43e7e-bf35-475d-b677-9dc61e549339}', 'SendEmail', [properties]);
- context.addQuery($v_0);
- };
- }
- function sendMail() {
- var ctx = SP.ClientContext.get_current();
- var emailProperties = new SP.Utilities.EmailProperties();
- emailProperties.set_to(['user1@company.com', 'user2@company.com']);
- emailProperties.set_from('user3@company.com');
- emailProperties.set_body('body');
- emailProperties.set_subject('subject');
- SP.Utilities.Utility.sendEmail(ctx, emailProperties);
- ctx.executeQueryAsync(
- function () {
- console.log("Mail sent");
- },
- function (sender, args) {
- console.log('Request failed. ' + args.get_message() + '\n' + args.get_stackTrace());
- }
- );
- }
- 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.
