My Idea Factory - By Ludovic Perrichon

SharePoint, AlertTemplates, SP2013

Alert template from scratch

You have to create an alert template for SharePoint. But you got lost in the documentation and the 12000 lines in the xml. In this post I will explain you how to create an Alert template from scratch. I did this post under SharePoint 2013. But it might also works for SharePoint 2010.

I am going to create an custom alert template for a custom list. This custom list as three fields :

  • Title
  • AlertUsers : a Yes/No field
  • PublishDate : a Date field

Firstable you have to go in the alert templates xml folder which is by default :

C:\Program Files\Common Files\microsoft shared\Web Server Extensions\15\TEMPLATE\XML

Make a copy of alerttemplates.xml and rename it. In my case alerttemplates_custom.xml.

The msdn link about alert templates : https://msdn.microsoft.com/fr-fr/library/bb802961(v=office.12).aspx.

We are going to create a new alert template. Open your copy of alerttemplte.xml, In between the tag AlertTemplates add at the end the following tag :

<AlertTemplate Type="List"  Name="SPAlertTemplateType.MyCustomListAlertTemplate"></AlertTemplate>

Inside AlertTemplate you have just created, add the following code :

<EventTypes IsVisible="True"/>  
<Format>
   <Digest></Digest>
   <Immediate></Immediate>
</Format>
<Properties>
</Properties>
<Filters IsVisible="True">
   <FilterDefinition>
   </FilterDefinition>
</Filters>

Before to go a bit deeper. Keep in mind that Immediate is for immediate alerts and digest is for daily and weekly alerts. I will come back on this points below.

Filters tag.
From the msdn link above Filters enable you to create new triggers for an event such as "the priority of a task has changed".

In others words, you know when you are on a list, you click on Alert me in the ribbon and a pop-in shows up to ask which kind of alert do you want :

  • Anything changes
  • Someone else changes an item
  • Someone else changes an item created by me
  • Someone else changes an item last modified by me

Well that’s the defaults by SharePoint and filter is where you can create an alert condition. Each one is described in a FilterDefinition.

The FilterDefinition tag look like this :

<FilterDefinition>  
    <FriendlyName>My Custom alert Friendly Name</FriendlyName>
    <ShortName>Custom alert Short Name</ShortName>
    <Query></Query>
<FilterDefinition>

In the templates you can see some

$Ressources :RessourceName
To know what it does return, go have a look in the ressource file. Ressource file is located in :
C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\15\Resources\core.resx

To fill the query tag you have to use CAML. This is a bit like working with CAML to get some list items, the difference, here we also work with old and new values of an item.

So I am going to create twho FilterDefinition :

  • The first one is to send all modified, deleted and new elements when, in the list, a boolean field Alert users is checked.
    <FriendlyName>For published elements where Alert user is checked</FriendlyName>  
    <ShortName>Alert User is checked and element is published<ShortName>
    <Query>
        <And>
            <Eq>
                <FieldRef name="AlertUsers/New"/>
                <Value type="Boolean">1</Value>
            </Eq>
            <Leq>
                <FieldRef Name="PublishDate/New" />
                <Value IncludeTimeValue='FALSE' Type='DateTime'>
                    <Today />
                </Value>
            </Leq>
        </And>
    </Query>
  • The second one is to send all modified, deleted and new elements when in, the list, a boolean field Alert users is checked and when a PublishDate is equal or after the current date.
    <Query>  
        <And>
            <Eq>
                <FieldRef name="AlertUsers/New"/>
                <Value type="Boolean">1</Value>
            </Eq>
            <Leq>
                <FieldRef Name="PublishDate/New" />
                <Value IncludeTimeValue='FALSE' Type='DateTime'>
                    <Today />
                </Value>
            </Leq>
        </And>
    </Query>

Did you notice the /New on the fieldref ? Well that’s normal. In alert template you have to work with the new value of the item ([FieldrefName]/New) and old value ([FieldrefName]/Old).

So far, my alert template xml look like this :

Properties tag.

Properties is use to exclude some fields in the Fields or RowFields of Format, I wilm explain it a bit below. To quickly describe Fields and RowFields, it works like a foreach fields in the current item.
If you don’t exclude some fields, the loop will go through all the fields.

  • To exclude field from the immidiate alerts, add the internal name in tag ImmediateNotificationExcludedFields. Add the following code into Format tag :
    <ImmediateNotificationExcludedFields>  
    ID;Author;Editor;Modified_x0020_By;Created_x0020_By;_UIVersionString;ContentType;TaskGroup;IsCurrent;Attachments;NumComments;  
    </ImmediateNotificationExcludedFields>
    For example, here, we did exclude fileds like ID, Author ... from the loop in the imidiate alerts.
     
  • To exclude fields from the daily and weekly alerts use the tag DigestNotificationExcludedFields. Add the following code into format (below ImmediateNotificationExcludedFields)
    <DigestNotificationExcludedFields>  
    ID;Author;Editor;Modified_x0020_By;Created_x0020_By;_UIVersionString;ContentType;TaskGroup;IsCurrent;Attachments;NumComments;  
    </DigestNotificationExcludedFields>
    For example, here, we did exclude fileds like ID, Author ... from the loop in the daily and weekly alerts.

Your properties should look like this now :

<Properties>  
   <ImmediateNotificationExcludedFields>    ID;Author;Editor;Modified_x0020_By;Created_x0020_By;_UIVersionString;ContentType;TaskGroup;IsCurrent;Attachments;NumComments;
   </ImmediateNotificationExcludedFields>
   <DigestNotificationExcludedFields>
ID;Author;Editor;Modified_x0020_By;Created_x0020_By; _UIVersionString;ContentType;TaskGroup;IsCurrent;Attachments; NumComments;  
   </DigestNotificationExcludedFields>
</Properties>

Format tag.
Format is where you will work on what your alert will look like.
I am going to explain each tags and then I will explain how to fill it and work with var inside this xml.
Inside Format you have Immediate and Digest.


  • Immediate is for the rendre of the immediate alert.

  • Digest is for the render of the daily and weekly alert.

Inside Digest, add the following elements :

<Digest>  
    <Subject>
    </Subject>
    <Header>
    </Header>
    <HeaderFieldsHeader>
    </HeaderFieldsHeader>
    <HeaderFields>
    </HeaderFields>
    <HeaderFieldsFooter>
    </HeaderFieldsFooter>
    <RowHeader>
    </RowHeader>
    <RowFields>
    </RowFields>
    <RowFooter>
    </RowFooter>
    <Footer>
    </Footer>
</Digest>
  • Subject : This is the subject of your mail.
  • Header : This is the header of your mail. There is no loop. Inside this one, you are going to find, of course, your html (only open), your head (open and close) and your body (only open) tags. This is also where you should put your css style.
  • HeaderFieldsHeader : Same as rowheader (below) but before the row tags.
  • HeaderFields : Same as RowFields (below) but before row tags.
  • HeaderFieldsFooter : Same as RowFooter (below) but before row tags.
  • RowHeader : It’s the header for each items return by your query. It’s gonning to loop on all items.
  • RowFields : This one going to loop over each items fields.
  • RowFooter : It’s the footer for each items return by your query. It’s gonning to loop on all items.
  • Footer : This is the header of your mail. There is no loop. Inside this one, you gonna find of course your html (only close) and your body (only close) tags.

If it’s not clear enought, your template will run this way.

The header
     Then for each items the row header
          Then for each fieds in the current item, the rowfields
     Then for each items the row footer
The footer

If it was with C# it will probably be something like (Unfortunately, it’s not) :

string email = string.Empty;  
email += "My header content";  
foreach (SPListItem items in ItemsCollectionReturnedByMyQuery)  
{
     email += "My rowheader content from the current item";
     foreach(SPField field in items.Fields){ // Only modified field
          email += "My rowfield content from the current field";
     }
     email += "My rowfooter content from the current item";
}
email += "My Footer content";  

Inside the Immediate tag, add the following content :

<Immediate>  
    <Subject>
    </Subject>
    <Header>
    </Header>
    <Fields>
    </Fields>
    <Footer>
    </Footer>
</Immediate>
  • Subject : This is the subject of the mail.
  • Header : This is the header of the mail. No loop on this one.
  • Fields : This is the loop which goes on every modified fileds.
  • Footer : This is the footer of the mail. No loop on this one.

If it was with C# it will probably be something like :

string email = string.Empty;  
email += "My header content";  
foreach(SPField field in items.Fields){ // Only modified field  
     email += "My Field content from the current field";
}
email += "My Footer content";  

Now inside these tags, how it s work ? You can get elements of your list items, set and get var and use conditions.

You can find all tags right here :

I am going to be honest, some tags sounds totally unknow to me. But I will present you some of them. The ones I usually use.

First how to write HTML. It has to be set in an HTML tag with CDATA :

<HTML><![CDATA[<html><head>…</head><body>…</body></html>]]></HTML>

Now to Set var :

<SetVar Name="MyVar" Scope = "Request">MyValue</SetVar>

Here I set var name MyVar with a value MyValue. It’s a bit like :

string MyVar = "MyValue";

Now to get the var :

<GetVar Name="MyVar"/>

It will return MyValue.

You can also get the new and old value of Fields :
To get the new value, use NewValue#MyField and the old value OldValue#MyField.

<GetVar Name="OldValue#MyField"/>  
<GetVar Name="NewValue#MyField"/>

About condition :
The equal condition IfEqual.
The not equal condition IfNeg.
The substring, like if string.indexOf("MyString") is not equal -1 IfSubstring.
I took the IfEqual for example below. You will see Expr1 and Expr2 which are use to compare two value. The Then tag to do something if the condition is true. The Else tag to do something if the condition are false.

<IfEqual>  
    <Expr1>
        <GetVar Name="OldValue#MyField"/>
    </Expr1>
    <Expr2>
        <GetVar Name="NewValue#MyField"/>
    </Expr2>
    <Then>
        [DO SOMETHING]
    </Then>
    <Else>
        [DO SOMETHING]
    </Else>
</IfEqual>

If I want to check two var :

<IfEqual>  
    <Expr1>
        <GetVar Name="MyVar1"/>
    </Expr1>
    <Expr2>
        <GetVar Name="MyVar2"/>
    </Expr2>
    <Then>
        [DO SOMETHING]
    </Then>
    <Else>
        [DO SOMETHING]
    </Else>
</IfEqual>

If you want to check a Field with a var :

<IfEqual>  
    <Expr1>
        <GetVar Name="NewValue#MyField"/>
    </Expr1>
    <Expr2>
        <GetVar Name="MyVar"/>
    </Expr2>
    <Then>
        [DO SOMETHING]
    </Then>
    <Else>
        [DO SOMETHING]
    </Else>
</IfEqual>

The Switch tag (https://msdn.microsoft.com/en-us/library/office/ms465598.aspx) contains an Expr tag, which contains the value of the var to switch. Then some case tags to do something if your value match the case and a Default tag, if your value doesn’t match any case.

<Switch>  
  <Expr><GetVar Name="MyVar"/></Expr>
  <Case Value="Value_1">[DO SOMETHING]</Case>
  <Case Value="Value_2">[DO SOMETHING]</Case>
  ...
  <Default>[DO SOMETHING]</Default>
</Switch>

Predefined var
If you have a look at some default SharePoint templates, you will see some predefined var. Here is a little table of some vars :

Var Return
AlertTitle The title of the user defined when he create the alert.
EventType Return a number.
1 : New
2 : Edited
4 : Deleted
Where is 3 ? Well I don’t know : https://msdn.microsoft.com/en-us/library/microsoft.sharepoint.speventtype.aspx
ItemName Title of the current item
ItemUrl Url of the current item
ListName Title of the current list
ListUrl Url of the current list
SiteName Title of the current site
SiteUrl Url of the current site

So this is the xml I have made (you want to see screenshots of the results, go to the end) :

<AlertTemplate Type="List"  Name="SPAlertTemplateType.MyCustomListAlertTemplate">  
    <EventTypes IsVisible="True"/>
    <Format>
        <Digest>
            <Subject>
                <HTML><![CDATA[My custom Digest Alert - ]]></HTML>
                <GetVar Name="AlertTitle" />
            </Subject>
            <Header>
                <HTML><![CDATA[
                    <html>
                        <head>
                            <style type="text/css">
                                .newValueDeleted{
                                    color: #ff0000;
                                }

                                .newValueNew{
                                    color: #009900;
                                }

                                .newValueModified{
                                    font-weight: bold;
                                }
                            </style>
                        </head>
                        <body>
                            <h2>My custom alert !!!</h2>
                            <div id="itemTitle">
                                You received an alert for <a href="]]></HTML><GetVar Name="ListUrl" URLEncodeAsURL="TRUE" /><HTML><![CDATA[">]]></HTML><GetVar Name="ListName" URLEncodeAsURL="TRUE" /><HTML><![CDATA[</a>
                            </div>            
                ]]></HTML>
            </Header>
            <HeaderFieldsHeader>
            </HeaderFieldsHeader>
            <HeaderFields>
            </HeaderFields>
            <HeaderFieldsFooter>
            </HeaderFieldsFooter>
            <RowHeader>
                <HTML><![CDATA[
                    <div class="item">
                        <div class="itemName">]]></HTML><GetVar Name="ItemName" HTMLEncode="TRUE"/>
                        <IfEqual>
                            <Expr1>
                                <GetVar Name="EventType"/>
                            </Expr1>
                            <Expr2>
                                <HTML>4</HTML>
                            </Expr2>
                            <Then>
                                <HTML><![CDATA[ - <span class="deleted">Deleted</span>]]></HTML>
                            </Then>
                            <Else>
                                <HTML><![CDATA[ - <a href="]]></HTML><GetVar Name="ItemUrl" HTMLEncode="TRUE"/><HTML><![CDATA[">Go to item</a>]]></HTML>
                            </Else>
                        </IfEqual>
                        <HTML><![CDATA[
                        </div>
                            <table>
                                <tr>
                                    <th>Field</th>
                                    <th>Old Value</th>
                                    <th>New Value</th>
                                </tr>
                ]]></HTML>
            </RowHeader>
            <RowFields>
                <HTML><![CDATA[
                    <tr class="field">
                        <td>]]></HTML><GetVar Name="DisplayName#{Field}" HTMLEncode="TRUE" /><HTML><![CDATA[</td>
                     ]]></HTML>
                         <Switch>
                            <Expr>
                              <GetVar Name="EventType"/>
                            </Expr>
                            <Case Value="2">
                              <IfEqual>
                                <Expr1>
                                  <GetVar Name="OldValue#{Field}"/>
                                </Expr1>
                                <Expr2>
                                  <GetVar Name="NewValue#{Field}"/>
                                </Expr2>
                                <Then>
                                <HTML><![CDATA[
                                    <td class="oldValueNotModified">-</td>
                                    <td class="newValueNotModified">]]></HTML><GetVar Name="NewValue#{Field}"/><HTML><![CDATA[</td>
                                ]]></HTML>
                                </Then>
                                <Else>
                                <HTML><![CDATA[
                                    <td class="oldValueModified">]]></HTML><GetVar Name="OldValue#{Field}"/><HTML><![CDATA[</td>
                                    <td class="newValueModified">]]></HTML><GetVar Name="NewValue#{Field}"/><HTML><![CDATA[</td>
                                ]]></HTML>
                                </Else>
                              </IfEqual>
                            </Case>
                            <Case Value="4">
                                <HTML><![CDATA[
                                    <td class="oldValueDeleted">]]></HTML><GetVar Name="OldValue#{Field}"/><HTML><![CDATA[</td>
                                    <td class="newValueDeleted">Deleted</td>
                                ]]></HTML>
                            </Case>
                            <Default>
                                <HTML><![CDATA[
                                    <td class="newValueNew">Added</td>
                                    <td class="oldValueNew">]]></HTML><GetVar Name="NewValue#{Field}"/><HTML><![CDATA[</td>
                                ]]></HTML>
                            </Default>
                          </Switch>
                     <HTML><![CDATA[
                    </tr>
                ]]></HTML>
            </RowFields>
            <RowFooter>
                <HTML><![CDATA[
                        </table>
                    </div>
                    <hr/>
                ]]></HTML>
            </RowFooter>
            <Footer>
                <HTML><![CDATA[
                            <a id="goToList" href="]]></HTML><GetVar Name="ListUrl" URLEncodeAsURL="TRUE" /><HTML><![CDATA[">Go to ]]></HTML><GetVar Name="ListName" HTMLEncode="True" /><HTML><![CDATA[</a>
                            <br/>
                            <a id="goToSite" href="]]></HTML><GetVar Name="SiteUrl" URLEncodeAsURL="TRUE" /><HTML><![CDATA[">Go to ]]></HTML><GetVar Name="SiteName"/><HTML><![CDATA[</a>
                        </body>
                    </html>
                ]]></HTML>
            </Footer>
        </Digest>
        <Immediate>
            <Subject>
                <HTML><![CDATA[My custom Immediate Alert - ]]></HTML>
                <GetVar Name="AlertTitle" />
                <HTML><![CDATA[ - ]]></HTML>
                <GetVar Name="ItemName" />
            </Subject>
            <Header>
                <HTML><![CDATA[
                    <html>
                        <head>
                            <style type="text/css">
                                .newValueDeleted{
                                    color: #ff0000;
                                }

                                .newValueNew{
                                    color: #009900;
                                }

                                .newValueModified{
                                    font-weight: bold;
                                }
                            </style>
                        </head>
                        <body>
                            <h2>My custom alert !!!</h2>
                            <div id="itemTitle">
                                The following item has been modified : <a href="]]></HTML><GetVar Name="ItemUrl" URLEncodeAsURL="TRUE" /><HTML><![CDATA[">]]></HTML><GetVar Name="ItemName" URLEncodeAsURL="TRUE" /><HTML><![CDATA[</a>
                            </div>
                            <table>
                                <tr>
                                    <th>Field name</th>
                                    <th>Old Value</th>
                                    <th>New Value</th>
                                </tr>
                ]]></HTML>
            </Header>
            <Fields>
                <HTML><![CDATA[
                            <tr>
                                <td>]]></HTML><GetVar Name="DisplayName#{Field}" HTMLEncode="TRUE" /><HTML><![CDATA[</td>
                     ]]></HTML>
                         <Switch>
                            <Expr>
                              <GetVar Name="EventType"/>
                            </Expr>
                            <Case Value="2">
                              <IfEqual>
                                <Expr1>
                                  <GetVar Name="OldValue#{Field}"/>
                                </Expr1>
                                <Expr2>
                                  <GetVar Name="NewValue#{Field}"/>
                                </Expr2>
                                <Then>
                                <HTML><![CDATA[
                                    <td class="oldValueNotModified">-</td>
                                    <td class="newValueNotModified">]]></HTML><GetVar Name="NewValue#{Field}"/><HTML><![CDATA[</td>
                                ]]></HTML>
                                </Then>
                                <Else>
                                <HTML><![CDATA[
                                    <td class="oldValueModified">]]></HTML><GetVar Name="OldValue#{Field}"/><HTML><![CDATA[</td>
                                    <td class="newValueModified">]]></HTML><GetVar Name="NewValue#{Field}"/><HTML><![CDATA[</td>
                                ]]></HTML>
                                </Else>
                              </IfEqual>
                            </Case>
                            <Case Value="4">
                                <HTML><![CDATA[
                                    <td class="oldValueDeleted">]]></HTML><GetVar Name="OldValue#{Field}"/><HTML><![CDATA[</td>
                                    <td class="newValueDeleted">Deleted</td>
                                ]]></HTML>
                            </Case>
                            <Default>
                                <HTML><![CDATA[
                                    <td class="newValueNew">Added</td>
                                    <td class="oldValueNew">]]></HTML><GetVar Name="NewValue#{Field}"/><HTML><![CDATA[</td>
                                ]]></HTML>
                            </Default>
                          </Switch>
                     <HTML><![CDATA[
                            </tr>
                ]]></HTML>
            </Fields>
            <Footer>
                <HTML><![CDATA[
                            </table>
                            </hr>
                            <a id="goToList" href="]]></HTML><GetVar Name="ListUrl" URLEncodeAsURL="TRUE" /><HTML><![CDATA[">Go to ]]></HTML><GetVar Name="ListName" HTMLEncode="True" /><HTML><![CDATA[</a>
                            <br/>
                            <a id="goToSite" href="]]></HTML><GetVar Name="SiteUrl" URLEncodeAsURL="TRUE" /><HTML><![CDATA[">Go to ]]></HTML><GetVar Name="SiteName"/><HTML><![CDATA[</a>
                        </body>
                    </html>
                ]]></HTML>
          </Footer>
        </Immediate>
    </Format>
    <Properties>
        <ImmediateNotificationExcludedFields>
            ID;Author;Editor;Modified_x0020_By;Created_x0020_By;_UIVersionString;ContentType;TaskGroup;IsCurrent;Attachments;NumComments;
        </ImmediateNotificationExcludedFields>
        <DigestNotificationExcludedFields>
            ID;Author;Editor;Modified_x0020_By;Created_x0020_By;_UIVersionString;ContentType;TaskGroup;IsCurrent;Attachments;NumComments;
        </DigestNotificationExcludedFields>
    </Properties>
    <Filters IsVisible="True">
       <FilterDefinition>
           <FriendlyName>For all elements where Alert user is checked</FriendlyName>
            <ShortName>Alert User is checked</ShortName>
            <Query>
                <Eq>
                    <FieldRef name="AlertUsers/New"/>
                    <Value type="Boolean">1</Value>
                </Eq>
            </Query>
       </FilterDefinition>
       <FilterDefinition>
           <FriendlyName>For published elements where Alert user is checked</FriendlyName>
            <ShortName>Alert User is checked and element is published</ShortName>
             <Query>
                <And>
                    <Eq>
                        <FieldRef name="AlertUsers/New"/>
                        <Value type="Boolean">1</Value>
                    </Eq>
                    <Leq>
                        <FieldRef Name="PublishDate/New" />
                        <Value IncludeTimeValue='FALSE' Type='DateTime'>
                            <Today />
                        </Value>
                    </Leq>
                </And>
            </Query>
       </FilterDefinition>
    </Filters>
</AlertTemplate>

Well, the xml is done. Now you have to deploy it.

On your server, run the following Powershell code. Don't forget to change your url, your path to your new xml template and your list.

Add-PSSnapin Microsoft.SharePoint.Powershell  
$siteUrl = "http:/ /urlOfMySite"

$contentService = [Microsoft.SharePoint.Administration.SPWebService]::ContentService 
stsadm -o updatealerttemplates -filename 'C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\15\TEMPLATE\XML\alerttemplates_custom.xml' -url $siteUrl  
$AlertsTemplateCollection =  new-object Microsoft.SharePoint.SPAlertTemplateCollection($contentService)

$spWeb = Get-SPWeb -Identity $siteUrl 
$splists = $spWeb.lists 
$Mylist = $splists["MyList"] 
$Mylist.AlertTemplate = $AlertsTemplateCollection["SPAlertTemplateType.MyCustomListAlertTemplate"] 
$Mylist.Update()

Now on your front end server(s). Restart the SharePoint Timer service and do iisreset.

That's it !

So now when I want to create my alert, I see the filters :

Here is the results : I edited few things on my list like below.

The immediate alerts :

And a daily alert :

Now if you need to create an alert template by code :
http://www.ludovicperrichon.com/create-sharepoint-alert-by-code/