Creating an Outdated Content Notification Task in Kentico


Having expired content on your site is the quickest way to kill traffic and interest. If users don’t see regular updates to your content, they’ll lose interest and likely not return. Ensuring their experience is fresh and relevant is the key to keeping your visitors interested and coming back. In this article, I’ll show you how to create a Scheduled Task in Kentico to notify your content owners when their pages are getting stale to ensure you provide the most engaging experience possible.

You can implement every SEO trick in the book, but if you’re content is old, you’re going to lose visitors. People frequent sites they love for a reason: to get new information. If your site isn’t providing that experience, you can be sure that your user base is going to find their information somewhere else. Keeping your content fresh and updated is a critical step in this engagement process.

Almost every bit of content in Kentico has a date for when it was last updated. This allows you to quickly identify the last time a page or form was modified by your editors. Using the Outdated pages module, you can view content across your site and see exactly what needs to be updated.

Outdated pages

Note: There is also the Page Reports / Expired pages report in the Reporting module, which you can subscribe users to.

While these options are great for some scenarios, I wanted to make the process a little more automated and personalized for my editors.

A Scheduled Task could surely help things, right?

Creating the task

The first step of the process was to create a new custom task for my functionality. I created a new class and added some basic code for reporting. I used the TaskInfo object to pass values into my custom code. I used these values to determine how the task functioned and what the results would be. In my case, I used the following values in the TaskInfo parameters:

  • (int) Number of days to check
  • (bool) Flag for determining if the task should send owner notifications
  • (bool) Flag for determining if detailed reporting should be in the task result

public class OutdatedContentNotifierTask : ITask { /// <summary> /// Executes the task. /// </summary> /// <param name="ti">Info object representing the scheduled task</param> public string Execute(TaskInfo ti) { StringBuilder sb = new StringBuilder(); try { // Get the task parameters string[] taskparams = ti.TaskData.Split(','); int intExpirationDays = ValidationHelper.GetInteger(taskparams[0], 0); bool blnSendNotification = ValidationHelper.GetBoolean(taskparams[1], false); bool blnDetail = ValidationHelper.GetBoolean(taskparams[2], false); } catch (Exception ex) { sb.Append("ERROR: " + ex.Message); } return sb.ToString(); } }

You find out more about creating custom tasks here.

Retrieving the outdated content

The next step was to add my task functionality. I wanted to accomplish the following:

  • Get the list of outdated documents based on a specific number of days
  • Get a unique list of owners
  • Send an email to each owner with their list of outdated content

To accomplish these goals, I planned to use the DocumentQuery functionality. This API would return the documents I search for, along with the node information. Because I wanted to send an email to the owners, I knew I would have to get the user information, as well. To achieve this, I created a new “coupled” class for my documents and owners called OutdatedDocument.

public class OutdatedDocument { public string DocumentName { get; set; } public string NodeAliasPath { get; set; } public DateTime DocumentModifiedWhen { get; set; } public int OwnerID { get; set; } public string OwnerUsername { get; set; } public string OwnerEmail { get; set; } public string OwnerFullname { get; set; } }

I created a new List<OutdatedDocument> object to hold my content.

List<OutdatedDocument> lstDocs = new List<OutdatedDocument>();

I then used the DocumentHelper.GetDocuments to retrieve my outdated content. For each document, I created a new OutdatedDocument object and retrieved to the UserInfo information for the NodeOwner. I then added the new object to my list. In retrieving the documents, I specified only the columns I would need.

// Get all the documents older than the specified number of days var outdateddocs = DocumentHelper.GetDocuments() .Columns("DocumentName", "DocumentModifiedWhen", "NodeOwner", "NodeAliasPath") .WhereLessThan("DocumentModifiedWhen", DateTime.Now.AddDays(-intExpirationDays)) .OnCurrentSite(); // Loop through the documents foreach (var outdateddoc in outdateddocs) { // Create a new object for each docuemnt, combining the document and owner data OutdatedDocument doc = new OutdatedDocument(); doc.DocumentName = outdateddoc.DocumentName; doc.DocumentModifiedWhen = outdateddoc.DocumentModifiedWhen; doc.NodeAliasPath = outdateddoc.NodeAliasPath; doc.OwnerID = outdateddoc.NodeOwner; // Get the owner info UserInfo uiOwner = UserInfoProvider.GetUserInfo(ValidationHelper.GetInteger(outdateddoc.NodeOwner, 0)); if (uiOwner != null) { doc.OwnerUsername = uiOwner.UserName; doc.OwnerFullname = uiOwner.FullName; doc.OwnerEmail= uiOwner.Email; } // Add the new object to the list lstDocs.Add(doc); }

I added the Total Outdated Content Items count to my task response.

// Set the total outdated content sb.Append("Total Outdated Content Items: " + outdateddocs.Count);

Creating a report for each owner

With my retrieved outdated content, I wanted to create a report for each owner. This would allow me to notify them individually with a list of all the documents owned by them that were older than the specified number of days.

First, I grouped my list of OutdatedDocument objects by NodeOwner.

// Get the list of unique owners from the collection var owners = lstDocs.GroupBy(t => t.OwnerUsername) .Select(grp => grp.First()) .ToList();

Next, I looped through each owner and filtered the list of objects for the owner. I then sorted the list by the DocumentModifiedWhen date. I also leveraged my blnDetail TaskInfo value to add the owner counts to the task response.

// Loop through each owner to generate a list of ther documents foreach (var owner in owners) { // Get all of docuemnts for the owner var docs = lstDocs.FindAll(n => n.OwnerID == owner.OwnerID); // Reorder list by oldest first docs.Sort((x, y) => x.DocumentModifiedWhen.CompareTo(y.DocumentModifiedWhen)); if (blnDetail) { // Add each owner's username/count to the task results sb.Append(" | "); if (owner.OwnerUsername != null) { sb.Append(owner.OwnerUsername); } else { sb.AppendLine("UNASSIGNED"); } sb.Append(" (" + docs.Count + ") | "); } ... }

Next, I looped through my filtered and sorted objects to build up the list for the owner.

// Build the list of outdated content for the owner sbOwner.Clear(); foreach (var doc in docs) { sbOwner.Append(doc.DocumentModifiedWhen.ToString() + " - " + doc.DocumentName + "<br />" + doc.NodeAliasPath); sbOwner.Append("<br /><br />"); }

Send the owner a report

The last step of the process was to send an email to the user with a list of their outdated content. To do this, I wanted to leverage an email template. For my demo, this was a basic design with a few variables passed in.

Email template

With this template in place, I added the following code to my task to pass the values to the MacroResolver and send the email.

// Determine if notifications should be sent if ((blnSendNotification) && (owner.OwnerUsername != null)) { // Send the report to the owner EmailTemplateInfo eti = EmailTemplateProvider.GetEmailTemplate("OutdatedContentTemplate", SiteContext.CurrentSiteName); // Add the custom variables to the MacroResolver MacroResolver mcr = MacroResolver.GetInstance(); mcr.SetNamedSourceData("NAME", owner.OwnerFullname); mcr.SetNamedSourceData("NUMBER_OF_DAYS", intExpirationDays); mcr.SetNamedSourceData("OUTDATED_CONTENT_COUNT", docs.Count); mcr.SetNamedSourceData("OUTDATED_CONTENT_LIST", sbOwner.ToString()); // Create message object EmailMessage message = new EmailMessage(); // Get sender from settings message.EmailFormat = EmailFormatEnum.Both; message.From = eti.TemplateFrom; // Do not send the e-mail if there is no sender specified if (message.From != "") { // Initialize message message.Recipients = owner.OwnerEmail; message.Subject = eti.TemplateSubject; // Send email via Email engine API EmailSender.SendEmailWithTemplateText(SiteContext.CurrentSiteName, message, eti, mcr, true); } }


To test the functionality, I registered the custom task and set the TaskInfo parameters. For the values, I used the following:

  • 365 (Number of days to check)
  • true (Enables sending notification emails to owners)
  • true (Enables detailed reporting in the task results

Register task

I ran the task and confirmed I got the correct Last result values.

Execute task

I then confirmed the email was created and sent to the owner with the list of outdated content.


Moving forward

Leveraging Scheduled Tasks is a great way to provide automation to your application. Using the above task, a schedule could be created to check your content on a weekly or monthly basis and inform owners when their content is expiring. This can help you keep your content fresh and ensure your users are getting the most out of their visits to your site. Good luck!

Get the code

Exported Email Template

Exported Scheduled Task

This blog is intended for informational purposes only and provides an example of one of the many ways to accomplish the described task. Always consult Kentico Documentation for the best practices and additional examples that may be more effective in your specific situation.

Share this article on   LinkedIn

Bryan Soltis

Hello. I am a Technical Evangelist here at Kentico and will be helping the technical community by providing guidance and best practices for all areas of the product. I might also do some karaoke. We'll see how the night goes...