In our company we have confidential information stored in SharePoint. Some politically effective people are pushing to have Alerts sent to their external email addresses. How can I send custom email Alerts with content to internal emails and Alerts without content to external email addresses?
-
Not to put too blunt of a point on this... but why not just have those people that need alerts routed to external addresses simply create a forwarding rule in Outlook based on some aspect of the message, like subject or content?– Dave WiseCommented Aug 29, 2013 at 21:58
-
That is a great solution. Forgive me for not including that in my question, but a company policy expressly forbids all auto-forwarding to email addresses outside the company. I knocked on the door and was not granted the exception.– ghangasCommented Aug 30, 2013 at 12:55
2 Answers
Create new Alert Templates
1 Create a copy of the out of the box templates, change the name, the id, and add the call to the custom handler to each one.
Make the custom templates have a “.Ext” at the end of their name.
The custom features also call a custom NotificationHandlerAssembly
using System; using System.Runtime.InteropServices; using Microsoft.SharePoint; using Microsoft.SharePoint.Administration; using System.Xml; namespace Prov.Shared.CCPersonalEmail.Features.AlertTemplates { public class AlertTemplatesEventReceiver : SPFeatureReceiver { public override void FeatureActivated(SPFeatureReceiverProperties properties) { //The Properties we will add to the Alert Template //(Since this feature lives in the same project with the //IAlertNotifyHandler we just refer to our own assembly) string ClassToAdd = "CCPersonalClass"; SPFeatureDefinition FeatDef = (SPFeatureDefinition)properties.Feature.Definition; string ourAssemblyName = FeatDef.ReceiverAssembly; SPWebApplication WebApplication = (SPWebApplication)properties.Feature.Parent; SPWebService WebService = WebApplication.WebService; SPAlertTemplateCollection colSPAT = new SPAlertTemplateCollection(WebService); foreach (SPAlertTemplate SPAT in colSPAT) { if (SPAT.Name.Contains(".Ext")) { SPAT.Delete(); } } foreach (SPAlertTemplate SPAT in colSPAT) { if (SPAT.Name.Contains("SPAlertTemplateType")) { SPAlertTemplate newSPAT = new SPAlertTemplate(); XmlDocument xmlSpit = new XmlDocument(); xmlSpit.LoadXml(SPAT.Xml.ToString()); //create node and add value XmlNode node = xmlSpit.CreateNode(XmlNodeType.Element, "Properties", string.Empty); //create 3 nodes XmlNode nodeAssembly = xmlSpit.CreateElement("NotificationHandlerAssembly"); XmlNode nodeClassName = xmlSpit.CreateElement("NotificationHandlerClassName"); XmlNode nodeProperties = xmlSpit.CreateElement("NotificationHandlerProperties"); nodeAssembly.InnerText = ourAssemblyName; nodeClassName.InnerText = ourAssemblyName.Substring(0,ourAssemblyName.IndexOf(",")) + "." + ClassToAdd; //add to parent node node.AppendChild(nodeAssembly); node.AppendChild(nodeClassName); node.AppendChild(nodeProperties); //add to elements collection xmlSpit.DocumentElement.AppendChild(node); newSPAT.Xml = xmlSpit.InnerXml; newSPAT.Name = SPAT.Name.ToString() + ".Ext"; newSPAT.Id = Guid.NewGuid(); colSPAT.Add(newSPAT); } } foreach (SPTimerServiceInstance timerservice in WebApplication.Farm.TimerService.Instances) { timerservice.Stop(); timerservice.Start(); } } public override void FeatureDeactivating(SPFeatureReceiverProperties properties) { SPWebApplication WebApplication = (SPWebApplication)properties.Feature.Parent; SPWebService WebService = WebApplication.WebService; SPAlertTemplateCollection colSPAT = new SPAlertTemplateCollection(WebService); foreach (SPAlertTemplate SPAT in colSPAT) { if (SPAT.Name.Contains(".Ext")) { SPAT.Delete(); } } } } }
Custom Notification Handler assembly
1 The class CCPersonalClass implements the IAlertNotifyHandler interface which contains the defined method OnNotification
Attempts to send the custom alert (if it fails send normal alert.)
Custom alert:
Gets the PersonalEmail field data from the User Information List
If Personal Email is empty it just sends normal alert
If personal email is populated
Send normal email to internal email
Send reduced email to external address
Replace
http://
withhttp://ext-
If sending external email remove all detail records and just leave headers.
{
using System;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Utilities;
using System.Text.RegularExpressions;
using Framework.Logger;
using System.Collections.Specialized;
namespace Shared.CCPersonalEmail
{
class CCPersonalClass: IAlertNotifyHandler
{
public bool OnNotification(SPAlertHandlerParams alertHandler)
{
using (new SPMonitoredScope("Shared.CCPersonalClass OnNotification"))
{
string siteUrl = alertHandler.siteUrl;
using (SPSite site = new SPSite(siteUrl + alertHandler.webUrl))
{
using (SPWeb web = site.OpenWeb())
{
try
{
return CustomAlertNotification(web, alertHandler, siteUrl);
}
catch
{
//if it fails due to configuration still the default alert should work
SPUtility.SendEmail(web, alertHandler.headers, alertHandler.body);
return false;
}
}
}
}
}
private bool CustomAlertNotification(SPWeb web, SPAlertHandlerParams alertHandler, string siteUrl)
{
using (new SPMonitoredScope("Shared.CCPersonalClass CustomAlertNotification"))
{
string PersonalEmail = string.Empty;
int eventCount = alertHandler.eventData.GetLength(0);
//Get the Personal Email from the User Information List
try
{
PersonalEmail = GetPersonalEmail(alertHandler.headers["to"], web);
}
catch (Exception e)
{
Logger.LogError(e, "CCPersonalClass");
}
//Send each alert in the alertHandler
for (int i = 0; i < eventCount; i++)
{
try
{
if (string.IsNullOrEmpty(PersonalEmail))
{
SPUtility.SendEmail(web, alertHandler.headers, alertHandler.body);
}
else
{
//Send a normal notification to the work email
SPUtility.SendEmail(web, alertHandler.headers, alertHandler.body);
//Send an abbreviated email to the external email
string txt = alertHandler.body.ToString().Replace("http://", "http://ext-");
//Digest messages- removing detail records
string strRegex = "<tr>\\s*<td class=\"[a-z]*?\"> <div class=\"nobr\">.*?</td>{0}s*</tr>|";
//Immediate messages- removing detail records
strRegex = strRegex + "<tr>\\s*<td class=\"formlabel\">.*<td class=\"altvb\"> </td>\\s*</tr>";
txt = Regex.Replace(txt, strRegex, string.Empty, RegexOptions.Singleline);
StringDictionary PersonalHeader = alertHandler.headers;
PersonalHeader["cc"] = PersonalEmail;
SPUtility.SendEmail(web, PersonalHeader, txt);
}
}
catch (Exception e)
{
Logger.LogError(e, "CCPersonalClass");
}
}
if (eventCount > 1)
return true;
else
return false;
}
}
private string GetPersonalEmail(string WorkEmail, SPWeb web)
{
using (new SPMonitoredScope("Shared.CCPersonalClass GetPersonalEmail"))
{
SPQuery SelectUser = new SPQuery();
string SelectUserQS = string.Format("<Query><Where><Eq><FieldRef Name='EMail' /><Value Type='Text'>{0}</Value></Eq></Where></Query>", WorkEmail);
SelectUser.Query = SelectUserQS;
SPList UserList = web.Site.RootWeb.Lists["User Information List"];
SPListItemCollection SelectedUserCol = UserList.GetItems(SelectUser);
SPListItem SelectedUser = SelectedUserCol[0];
return (string)SelectedUser["PersonalEmail"];
}
}
}
}
Set the alerts to use the external template
1 Existing content is updated with Feature event receiver:
Existing alerts:
- Site.allwebs.alerts get flipped to the same template name they had with .ext appended
Existing Lists:
- Site.allwebs.lists flipped the attached alerttemplates to their .ext counterpart
{
using System;
using System.Runtime.InteropServices;
using System.Security.Permissions;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Security;
using Prov.Framework.Logger;
namespace Prov.Shared.CCPersonalEmail.Features.CCAlerts
{
[Guid("a3889d39-ea91-441e-9039-157c512441e4")]
public class CCAlertsEventReceiver : SPFeatureReceiver
{
// Set AlertTemplates on sites and alerts to custom templates.
public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
using (SPSite site = (SPSite)properties.Feature.Parent)
{
AlertTemplateSelector objSelectedAlertTemplate = new AlertTemplateSelector(site);
//Add PersonalEmail field if it does not exist yet
SPFieldCollection UserInfoFields = site.RootWeb.Lists["User Information List"].Fields;
try
{
bool FieldExists = UserInfoFields.ContainsField("PersonalEmail");
if (!FieldExists)
{
UserInfoFields.Add("PersonalEmail", SPFieldType.Text, false);
}
}
catch (Exception e)
{
Logger.LogError(e, "CCPersonalClass");
}
foreach (SPWeb web in site.AllWebs)
{
using (web)
{
web.AllowUnsafeUpdates = true;
foreach (SPList List in web.Lists)
{
try
{
List.AlertTemplate = objSelectedAlertTemplate.SwapAlertTemplate(List.AlertTemplate.Name, true);
List.Update();
}
catch (Exception e)
{
Logger.LogError(e, "CCAlertsEventReceiver");
}
}
foreach (SPAlert Alert in web.Alerts)
{
try
{
Alert.AlertTemplate = objSelectedAlertTemplate.SwapAlertTemplate(Alert.AlertTemplate.Name, true);
Alert.Update();
}
catch (Exception e)
{
Logger.LogError(e, "CCAlertsEventReceiver");
}
}
web.AllowUnsafeUpdates = false;
}
}
}
}
// Revert AlertsTemplates on sites and alerts back to out of box templates.
public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
{
using (SPSite site = (SPSite)properties.Feature.Parent)
{
AlertTemplateSelector objSelectedAlertTemplate = new AlertTemplateSelector(site);
foreach (SPWeb web in site.AllWebs)
{
using (web)
{
web.AllowUnsafeUpdates = true;
foreach (SPList List in web.Lists)
{
try
{
List.AlertTemplate = objSelectedAlertTemplate.SwapAlertTemplate(List.AlertTemplate.Name, false);
List.Update();
}
catch (Exception e)
{
Logger.LogError(e, "CCAlertsEventReceiver");
}
}
foreach (SPAlert Alert in web.Alerts)
{
try
{
Alert.AlertTemplate = objSelectedAlertTemplate.SwapAlertTemplate(Alert.AlertTemplate.Name, false);
Alert.Update();
}
catch (Exception e)
{
Logger.LogError(e, "CCAlertsEventReceiver");
}
}
web.AllowUnsafeUpdates = false;
}
}
}
}
}
}
The following code is used by feature activate, deactivate and the OnListCreation event handler
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SharePoint.Administration;
using Microsoft.SharePoint;
namespace Shared.CCPersonalEmail
{
class AlertTemplateSelector
{
private SPAlertTemplateCollection atc;
private SPSite site;
public AlertTemplateSelector(SPSite site)
{
this.site = site;
this.atc = new SPAlertTemplateCollection((SPWebService)site.WebApplication.Parent);
}
public SPAlertTemplate SwapAlertTemplate(string PreviousAlert, bool isActivating)
{
if ((!PreviousAlert.EndsWith(".Ext")) && (isActivating))
{
PreviousAlert = string.Format("{0}.Ext", PreviousAlert);
}
if ((PreviousAlert.EndsWith(".Ext")) && (!isActivating))
{
PreviousAlert = PreviousAlert.Replace(".Ext", "");
}
SPAlertTemplate newTemplate = atc[PreviousAlert];
if (newTemplate == null)
{
if (isActivating)
{
newTemplate = atc["SPAlertTemplateType.GenericList.Ext"];
}
else
{
newTemplate = atc["SPAlertTemplateType.GenericList"];
}
}
return newTemplate;
}
}
}
2 Future webs and their Future lists
- SPListEventReceiver ListAdded() event flips the template on all lists created in the site collection
{
namespace Shared.CCPersonalEmail.OnListCreation
{
public class OnListCreation : SPListEventReceiver
{
public override void ListAdded(SPListEventProperties properties)
{
base.ListAdded(properties);
SPWeb web = properties.Web;
SPList List = web.Lists[properties.ListId];
AlertTemplateSelector objSelectedAlertTemplate = new AlertTemplateSelector(web.Site);
List.AlertTemplate = objSelectedAlertTemplate.SwapAlertTemplate(List.AlertTemplate.Name, true);
List.Update();
}
}
}
Housekeeping
1 During feature activation
create a PersonalEmail field on the rootweb’s User Information List
Code is included in the feature activated event receiver above
2 During feature deactivation
- PersonalEmail becomes content so it is not removed upon deactivation
If it's isolated to just a single list, you might be able to use a workflow instead. You could create a holding list to track the type of thing to email on and a semicolon delimited list of email addresses. When an item is created or edited (whatever your alert logic should be) you look up the appropriate list of email addresses, and email that group the sanitized email.
-
The pilot program was a single web. I recommended to the site owner that they us this route. It does not scale well though.– ghangasCommented Aug 30, 2013 at 12:57